Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
df35103
Merge pull request #10 from ChainSafe/feat/boilerplate
dhyaniarun1993 Jan 14, 2025
25ba88e
added opcode and diassembler modules (#14)
ohmpatel1997 Jan 22, 2025
16a3566
feat: syscall parsing and analysing (#15)
sadiq1971 Jan 23, 2025
7f75029
fix: added cannon profile for syscall (#22)
sadiq1971 Jan 29, 2025
458b754
feat: added cannon opcodes (#18)
ohmpatel1997 Jan 31, 2025
f5ac875
feat: assembly parser supporting syscall (#24)
sadiq1971 Feb 3, 2025
b22b34d
added report output path flag (#25)
ohmpatel1997 Feb 3, 2025
4d16847
added github workflow for lint and test (#26)
ohmpatel1997 Feb 5, 2025
1c0e650
fix: use llvm-objdump for dissembling (#27)
sadiq1971 Feb 5, 2025
739466c
fix: tracer fixed
sadiq1971 Feb 10, 2025
3c71793
fix: removed irrevalent files
sadiq1971 Feb 10, 2025
289b2a2
fix: merged dev
sadiq1971 Feb 10, 2025
1fa154c
fix: updated sample file
sadiq1971 Feb 10, 2025
2394812
fix: added combined workflow
sadiq1971 Feb 10, 2025
f8e5bd8
feat: readme updated
sadiq1971 Feb 10, 2025
badf339
Merge branch 'dev' of github.com:ChainSafe/vm-compat into fix/syscall
sadiq1971 Feb 10, 2025
a9f4629
Merge branch 'dev' of github.com:ChainSafe/vm-compat into fix/syscall
sadiq1971 Feb 10, 2025
3f777b4
feat: cli updated- added trace option and command
sadiq1971 Feb 14, 2025
3d0e0d3
feat: readme updated
sadiq1971 Feb 14, 2025
d9885a1
Merge branch 'dev' of github.com:ChainSafe/vm-compat into fix/syscall
sadiq1971 Feb 14, 2025
070691e
fix: go code entry point
sadiq1971 Feb 14, 2025
9902013
feat: implemented lifo stack to generate stack trace with efficiently
sadiq1971 Feb 18, 2025
bd567e1
feat: indirect syscall detection in assembly
sadiq1971 Feb 20, 2025
4a0b74c
fix: test
sadiq1971 Feb 20, 2025
74409a3
fix: linter version updated
sadiq1971 Feb 20, 2025
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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ get:
.PHONY: get_lint
get_lint:
@if [ ! -f ./bin/golangci-lint ]; then \
curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.57.2; \
curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.63.0; \
fi;

.PHONY: lint
Expand Down
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ make analyser
#### Analyze Command

```sh
./bin/analyzer analyze [command options]
./bin/analyzer analyze [command options] arg[source path]
```

#### Analyze Options
Expand All @@ -111,7 +111,7 @@ make analyser
#### Trace Command

```sh
./bin/analyzer trace [command options]
./bin/analyzer trace [command options] arg[source path]
```

#### Trace Options
Expand All @@ -120,7 +120,8 @@ make analyser
|-----------------------|----------------------------------------------------------------------------------------|---------|
| `--vm-profile value` | Path to the VM profile config file (required). | None |
| `--function value` | Name of the function to trace. Include package name (e.g., `syscall.read`). (required) | None |
| `--help, -h` | Show help. | None |
| `--source-type value` | Assembly or go source code. | None |
| `--help, -h` | Show help. | None |

## Example Usage

Expand All @@ -134,7 +135,7 @@ make analyser
### Running a Trace

```sh
./bin/analyzer trace --vm-profile=./profile/cannon/cannon-64.yaml --function=syscall.read
./bin/analyzer trace --vm-profile=./profile/cannon/cannon-64.yaml --function=syscall.read sample.asm

````

Expand Down
50 changes: 38 additions & 12 deletions analyzer/opcode/opcode.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,11 @@ func NewAnalyser(profile *profile.VMProfile) analyzer.Analyzer {
}

func (op *opcode) Analyze(path string, withTrace bool) ([]*analyzer.Issue, error) {
var err error
var callGraph asmparser.CallGraph

switch op.profile.GOARCH {
case "mips32", "mips64":
callGraph, err = mips.NewParser().Parse(path)
default:
return nil, fmt.Errorf("unsupported GOARCH %s", op.profile.GOARCH)
}
callGraph, err := op.buildCallGraph(path)
if err != nil {
return nil, err
}

absPath, err := filepath.Abs(path)
if err != nil {
return nil, err
Expand All @@ -43,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(absPath, callGraph, segment.Label())
source, err := common.TraceAsmCaller(absPath, callGraph, segment.Label(), endCondition)
if err != nil { // non-reachable portion ignored
continue
}
Expand All @@ -62,11 +55,37 @@ func (op *opcode) Analyze(path string, withTrace bool) ([]*analyzer.Issue, error
return issues, nil
}

func (op *opcode) buildCallGraph(path string) (asmparser.CallGraph, error) {
var (
err error
callGraph asmparser.CallGraph
)

// Select the correct parser based on architecture.
switch op.profile.GOARCH {
case "mips32", "mips64":
callGraph, err = mips.NewParser().Parse(path)
default:
return nil, fmt.Errorf("unsupported GOARCH: %s", op.profile.GOARCH)
}
if err != nil {
return nil, fmt.Errorf("error parsing assembly file: %w", err)
}
return callGraph, nil
}

// TraceStack generates callstack for a function to debug
func (op *opcode) TraceStack(path string, function string) (*analyzer.IssueSource, error) {
return nil, fmt.Errorf("stack trace is not supported for assembly code")
graph, err := op.buildCallGraph(path)
if err != nil {
return nil, err
}
absPath, err := filepath.Abs(path)
if err != nil {
return nil, err
}
return common.TraceAsmCaller(absPath, graph, function, endCondition)
}

func (op *opcode) isAllowedOpcode(opcode, funct string) bool {
return slices.ContainsFunc(op.profile.AllowedOpcodes, func(instr profile.OpcodeInstruction) bool {
if !strings.EqualFold(instr.Opcode, opcode) {
Expand All @@ -80,3 +99,10 @@ func (op *opcode) isAllowedOpcode(opcode, funct string) bool {
})
})
}

func endCondition(function string) bool {
return function == "runtime.rt0_go" || // start point of a go program
function == "main.main" || // main
strings.Contains(function, ".init.") || // all init functions
strings.HasSuffix(function, ".init") // vars
}
112 changes: 64 additions & 48 deletions analyzer/syscall/asm_syscall.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"path/filepath"
"slices"
"strings"

"github.com/ChainSafe/vm-compat/analyzer"
"github.com/ChainSafe/vm-compat/asmparser"
Expand All @@ -13,8 +14,6 @@ import (
"github.com/ChainSafe/vm-compat/profile"
)

var syscallAPISForAsm = append(syscallAPIs, "runtime/internal/syscall.Syscall6")

// asmSyscallAnalyser analyzes system calls in assembly files.
type asmSyscallAnalyser struct {
profile *profile.VMProfile
Expand All @@ -29,76 +28,93 @@ func NewAssemblySyscallAnalyser(profile *profile.VMProfile) analyzer.Analyzer {
//
//nolint:cyclop
func (a *asmSyscallAnalyser) Analyze(path string, withTrace bool) ([]*analyzer.Issue, error) {
var (
err error
callGraph asmparser.CallGraph
)

// Select the correct parser based on architecture.
switch a.profile.GOARCH {
case "mips32", "mips64":
callGraph, err = mips.NewParser().Parse(path)
default:
return nil, fmt.Errorf("unsupported GOARCH: %s", a.profile.GOARCH)
}
callGraph, err := a.buildCallGraph(path)
if err != nil {
return nil, fmt.Errorf("error parsing assembly file: %w", err)
return nil, err
}

issues := make([]*analyzer.Issue, 0)

absPath, err := filepath.Abs(path)
if err != nil {
return nil, err
}

issues := make([]*analyzer.Issue, 0)
// Iterate through segments and check for syscall.
for _, segment := range callGraph.Segments() {
segmentLabel := segment.Label()
for _, instruction := range segment.Instructions() {
if !instruction.IsSyscall() {
continue
}
// Ignore indirect syscall calling from syscall apis
if slices.Contains(syscallAPISForAsm, segmentLabel) {
continue
}
syscallNum, err := segment.RetrieveSyscallNum(instruction)
syscalls, err := callGraph.RetrieveSyscallNum(segment, instruction)
if err != nil {
return nil, fmt.Errorf("failed to retrieve syscall number: %w", err)
}
for _, syscall := range syscalls {
// Categorize syscall
if slices.Contains(a.profile.AllowedSycalls, syscall.Number) {
continue
}
source, err := common.TraceAsmCaller(absPath, callGraph, syscall.Segment.Label(), endCondition)
if err != nil { // non-reachable portion ignored
continue
}
if !withTrace {
source.CallStack = nil
}

// Categorize syscall
if slices.Contains(a.profile.AllowedSycalls, syscallNum) {
continue
}
// Better to develop a new algo to check all segments at once like go_syscall
source, err := common.TraceAsmCaller(absPath, callGraph, segment.Label())
if err != nil { // non-reachable portion ignored
continue
}
if !withTrace {
source.CallStack = nil
}
severity := analyzer.IssueSeverityCritical
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
}

severity := analyzer.IssueSeverityCritical
message := fmt.Sprintf("Potential Incompatible Syscall Detected: %d", syscallNum)
if slices.Contains(a.profile.NOOPSyscalls, syscallNum) {
message = fmt.Sprintf("Potential NOOP Syscall Detected: %d", syscallNum)
severity = analyzer.IssueSeverityWarning
issues = append(issues, &analyzer.Issue{
Severity: severity,
Message: message,
Sources: source,
})
}

issues = append(issues, &analyzer.Issue{
Severity: severity,
Message: message,
Sources: source,
})
}
}
return issues, nil
}

func (a *asmSyscallAnalyser) buildCallGraph(path string) (asmparser.CallGraph, error) {
var (
err error
callGraph asmparser.CallGraph
)

// Select the correct parser based on architecture.
switch a.profile.GOARCH {
case "mips32", "mips64":
callGraph, err = mips.NewParser().Parse(path)
default:
return nil, fmt.Errorf("unsupported GOARCH: %s", a.profile.GOARCH)
}
if err != nil {
return nil, fmt.Errorf("error parsing assembly file: %w", err)
}
return callGraph, nil
}

// TraceStack generates callstack for a function to debug
func (a *asmSyscallAnalyser) TraceStack(path string, function string) (*analyzer.IssueSource, error) {
return nil, fmt.Errorf("stack trace is not supported for assembly code")
graph, err := a.buildCallGraph(path)
if err != nil {
return nil, err
}

absPath, err := filepath.Abs(path)
if err != nil {
return nil, err
}
return common.TraceAsmCaller(absPath, graph, function, endCondition)
}

func endCondition(function string) bool {
return function == "runtime.rt0_go" || // start point of a go program
function == "main.main" || // main
strings.Contains(function, ".init.") || // all init functions
strings.HasSuffix(function, ".init") // vars
}
Loading
Loading