goot

module
v0.0.21 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 12, 2022 License: MIT

README

goot

What is goot?

goot is a static analysis framework for Go. goot is easy-to-learn, easy-to-use and highly extensible, allowing you to easily develop new analyses on top of it.

Currently, goot provides the following major analysis components (and more analyses are on the way):

  • Control/Data-flow analysis framework
    • Control-flow graph construction
    • Classic data-flow analyses, e.g., constant propagtion analysis, taint passthrough analysis
    • Your dataflow analyses

Get started

First intall goot by

go get -u github.com/cokeBeer/goot

Then you can copy examples from package cmd to your project
For example, copy cmd/constantpropagationanalysis

package main

import (
	"github.com/cokeBeer/goot/pkg/example/dataflow/constantpropagation"
)

const src = `package main

func Hello(a int, b int) bool {
	a = 1
	x := a + 3
	y := b + 2
	if x > y {
		x = x + 1
	} else {
		x = y + 1
	}
	w := x > 0
	return w
}`

func main() {
	runner := &constantpropagation.Runner{Src: src, Function: "Hello"}
	runner.Run()
}

Run the code, and you will get a constant propagtion analysis result output to console

Use taint analysis

Write code below in your project

package main

import "github.com/cokeBeer/goot/pkg/example/dataflow/taint"

func main() {
	// if this file is cmd/taint/main.go
	// and you want analyse package pkg
	// the path should be "../../pkg"
	// or "../../pkg..." for all packages under pkg
	runner := taint.NewRunner("relative/path/to/package")
	// for this project, is "github.com/cokeBeer/goot"
	runner.ModuleName = "module-name"
	runner.PassThroughSrcPath = ""
	runner.PassThroughDstPath = "passthrough.json"
	runner.CallGraphDstPath = "callgraph.json"
	runner.PassThroughOnly = false
	runner.InitOnly = false
	runner.Debug = true
	runner.Run()
}

Run the code, and you will get a passthrough.json in the same directory, which contains taint passthrough information of all functions in your project
You can see key fmt.Sprintf holds the value [[0,1],[0],[1]]

{
    "fmt.Sprintf": [
        [0, 1], [0], [1]
    ]
}

This means the first parameter's taint and the second parameter's taint are passed to the first return value, the first parameter receives the first parameter's taint and the second parameter receives the second parameter's taint
Also, you will get a callgraph.json in the same directory
You can see the json file contains taint edges from one call parameter to another call parameter

{
    "(*github.com/cokeBeer/goot/pkg/bench.cleaner).startProcessing#0#(*os/exec.Cmd).StdoutPipe#0": {
        "From": "(*github.com/cokeBeer/goot/pkg/bench.cleaner).startProcessing",
        "FromIndex": 0,
        "To": "(*os/exec.Cmd).StdoutPipe",
        "ToIndex": 0,
        "ToIsMethod": false,
        "ToIsSink": true,
        "ToIsSignature": false,
        "ToIsStatic": true
    }
}

This means there is a taint edge from position 0 of startProcessing (in this case, the parameter is the receiver bench.cleaner itself ) to position 0 of StdoutPipe (in this case, the parameter is ther recevier exec.Cmd iteself, too)

Use as a framework

To use goot as a framework, first create two structs implementing pkg/toolkits/scalar.FlowAnalysis interface

// FlowAnalysis represents a flow analysis
type FlowAnalysis interface {
	GetGraph() *graph.UnitGraph
	IsForward() bool
	Computations() int
	FlowThrougth(inMap *map[any]any, unit ssa.Instruction, outMap *map[any]any)
	NewInitalFlow() *map[any]any
	EntryInitalFlow() *map[any]any
	Copy(srcMap *map[any]any, dstMap *map[any]any)
	MergeInto(Unit ssa.Instruction, inout *map[any]any, in *map[any]any)
	End(universe []*entry.Entry)
}

and pkg/golang/switcher.Switcher interface seperately

// Switcher represents a ssa instruction switcher
type Switcher interface {
	CaseAlloc(inst *ssa.Alloc)
	CasePhi(inst *ssa.Phi)
	CaseCall(inst *ssa.Call)
	CaseBinOp(inst *ssa.BinOp)
	CaseUnOp(inst *ssa.UnOp)
	...
	CaseGo(inst *ssa.Go)
	CaseDefer(inst *ssa.Defer)
	CaseSend(inst *ssa.Send)
	CaseStore(inst *ssa.Store)
	CaseMapUpdate(inst *ssa.MapUpdate)
	CaseDebugRef(inst *ssa.DebugRef)
}

Don't worry for these apis. An easy way to implement them is using compose like pkg/toolkits/scalar.BaseFlowAnalysis

// ConstantPropagationAnalysis represents a constant propagtion analysis
type ConstantPropagationAnalysis struct {
	scalar.BaseFlowAnalysis
	constantPropagationSwitcher *ConstantPropagationSwitcher
}

and pkg/golang/switcher.BaseSwitcher

// ConstantPropagationSwitcher represents a constant propagtion switcher
type ConstantPropagationSwitcher struct {
	switcher.BaseSwitcher
	constanctPropagationAnalysis *ConstantPropagationAnalysis
	inMap                        *map[any]any
	outMap                       *map[any]any
}

These can make you focus on the core methods you really need to design carefully in specific analyses

Some examples can be found in pkg/example/dataflow package

And you can learn how to run an analysis from cmd package

Presentation

This is the output of cmd/constantpropagationanalysis
The first part is the SSA format of the source code and the second part is the constant propagation on SSA

# Name: constantpropagtionanalysis.Hello
# Package: constantpropagtionanalysis
# Location: 3:6
func Hello(a int, b int) bool:
0:                                                                entry P:0 S:2
        t0 = 1:int + 3:int                                                  int
        t1 = b + 2:int                                                      int
        t2 = t0 > t1                                                       bool
        if t2 goto 1 else 3
1:                                                              if.then P:1 S:1
        t3 = t0 + 1:int                                                     int
        jump 2
2:                                                              if.done P:2 S:0
        t4 = phi [1: t3, 3: t6] #x                                          int
        t5 = t4 > 0:int                                                    bool
        return t5
3:                                                              if.else P:1 S:1
        t6 = t1 + 1:int                                                     int
        jump 2

constant fact for instruction: 1:int + 3:int
a=UNDEF b=UNDEF t0=4 

constant fact for instruction: b + 2:int
a=UNDEF b=UNDEF t0=4 t1=UNDEF 

constant fact for instruction: t0 > t1
a=UNDEF b=UNDEF t0=4 t1=UNDEF t2=UNDEF 

constant fact for instruction: if t2 goto 1 else 3
a=UNDEF b=UNDEF t0=4 t1=UNDEF t2=UNDEF 

constant fact for instruction: t1 + 1:int
a=UNDEF b=UNDEF t0=4 t1=UNDEF t2=UNDEF t6=UNDEF 

constant fact for instruction: jump 2
a=UNDEF b=UNDEF t0=4 t1=UNDEF t2=UNDEF t6=UNDEF 

constant fact for instruction: t0 + 1:int
a=UNDEF b=UNDEF t0=4 t1=UNDEF t2=UNDEF t3=5 

constant fact for instruction: jump 2
a=UNDEF b=UNDEF t0=4 t1=UNDEF t2=UNDEF t3=5 

constant fact for instruction: phi [1: t3, 3: t6] #x
a=UNDEF b=UNDEF t0=4 t1=UNDEF t2=UNDEF t3=5 t4=5 t6=UNDEF 

constant fact for instruction: t4 > 0:int
a=UNDEF b=UNDEF t0=4 t1=UNDEF t2=UNDEF t3=5 t4=5 t5=NAC t6=UNDEF 

constant fact for instruction: return t5
a=UNDEF b=UNDEF t0=4 t1=UNDEF t2=UNDEF t3=5 t4=5 t5=NAC t6=UNDEF 

Tips

  • goot's api is similar to soot, so if you wonder how goot's api work, you can learn soot first
  • goot uses *map[any]any as flow and ssa.Instruction as unit, so please be careful of type assertion

Thanks

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL