diff --git a/Makefile b/Makefile index 16eaf81d0a..02e961f3aa 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,7 @@ lib: (cd k8s_util/lib && make) + (cd ssa && make) clean: - (cd k8s_util/lib && make clean) \ No newline at end of file + (cd k8s_util/lib && make clean) + (cd ssa && make) \ No newline at end of file diff --git a/ssa/.gitignore b/ssa/.gitignore index 83f872b2c5..492467741c 100644 --- a/ssa/.gitignore +++ b/ssa/.gitignore @@ -1,2 +1,3 @@ .idea -.log \ No newline at end of file +.log +*.so \ No newline at end of file diff --git a/ssa/Makefile b/ssa/Makefile new file mode 100644 index 0000000000..cf7a83de3d --- /dev/null +++ b/ssa/Makefile @@ -0,0 +1,7 @@ +.PHONY: analysis + +analysis: + go build -buildmode=c-shared -o analysis.so ssa.go + +clean: + rm ./analysis.so \ No newline at end of file diff --git a/ssa/analysis.h b/ssa/analysis.h new file mode 100644 index 0000000000..0a1e6e6b61 --- /dev/null +++ b/ssa/analysis.h @@ -0,0 +1,75 @@ +/* Code generated by cmd/cgo; DO NOT EDIT. */ + +/* package command-line-arguments */ + + +#line 1 "cgo-builtin-export-prolog" + +#include /* for ptrdiff_t below */ + +#ifndef GO_CGO_EXPORT_PROLOGUE_H +#define GO_CGO_EXPORT_PROLOGUE_H + +#ifndef GO_CGO_GOSTRING_TYPEDEF +typedef struct { const char *p; ptrdiff_t n; } _GoString_; +#endif + +#endif + +/* Start of preamble from import "C" comments. */ + + + + +/* End of preamble from import "C" comments. */ + + +/* Start of boilerplate cgo prologue. */ +#line 1 "cgo-gcc-export-header-prolog" + +#ifndef GO_CGO_PROLOGUE_H +#define GO_CGO_PROLOGUE_H + +typedef signed char GoInt8; +typedef unsigned char GoUint8; +typedef short GoInt16; +typedef unsigned short GoUint16; +typedef int GoInt32; +typedef unsigned int GoUint32; +typedef long long GoInt64; +typedef unsigned long long GoUint64; +typedef GoInt64 GoInt; +typedef GoUint64 GoUint; +typedef __SIZE_TYPE__ GoUintptr; +typedef float GoFloat32; +typedef double GoFloat64; +typedef float _Complex GoComplex64; +typedef double _Complex GoComplex128; + +/* + static assertion to make sure the file is being used on architecture + at least with matching size of GoInt. +*/ +typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1]; + +#ifndef GO_CGO_GOSTRING_TYPEDEF +typedef _GoString_ GoString; +#endif +typedef void *GoMap; +typedef void *GoChan; +typedef struct { void *t; void *v; } GoInterface; +typedef struct { void *data; GoInt len; GoInt cap; } GoSlice; + +#endif + +/* End of boilerplate cgo prologue. */ + +#ifdef __cplusplus +extern "C" { +#endif + +extern char* Analyze(char* projectPathPtr, char* seedTypePtr, char* seedPkgPtr); + +#ifdef __cplusplus +} +#endif diff --git a/ssa/analysis.py b/ssa/analysis.py new file mode 100644 index 0000000000..530d51db72 --- /dev/null +++ b/ssa/analysis.py @@ -0,0 +1,15 @@ +import ctypes +import json + +def analyze(project_path: str, seed_type: str, seed_pkg: str) -> dict: + analysis_lib = ctypes.cdll.LoadLibrary('ssa/analysis.so') + analyze_func = analysis_lib.Analyze + analyze_func.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_char_p] + analyze_func.restype = ctypes.c_void_p + + analysis_result = analyze_func(project_path.encode("utf-8"), seed_type.encode("utf-8"), seed_pkg.encode("utf-8")) + analysis_result_bytes = ctypes.string_at(analysis_result) + return json.loads(analysis_result_bytes) + +if __name__ == '__main__': + print(analyze('/home/tyler/redis-operator/cmd/redisoperator', 'RedisFailover', 'github.com/spotahome/redis-operator/api/redisfailover/v1')) \ No newline at end of file diff --git a/ssa/ssa.go b/ssa/ssa.go index c9cd31f92e..4fe9240ceb 100644 --- a/ssa/ssa.go +++ b/ssa/ssa.go @@ -1,5 +1,6 @@ package main +import "C" import ( "flag" "fmt" @@ -9,12 +10,80 @@ import ( "encoding/json" + "io/ioutil" + "github.com/xlab-uiuc/acto/ssa/analysis" "github.com/xlab-uiuc/acto/ssa/util" "golang.org/x/tools/go/packages" "golang.org/x/tools/go/ssa/ssautil" ) +//export Analyze +func Analyze(projectPathPtr *C.char, seedTypePtr *C.char, seedPkgPtr *C.char) *C.char { + projectPath := C.GoString(projectPathPtr) + seedType := C.GoString(seedTypePtr) + seedPkg := C.GoString(seedPkgPtr) + + log.SetOutput(ioutil.Discard) + + analysisResult := analyze(projectPath, seedType, seedPkg) + return C.CString(analysisResult) +} + +func analyze(projectPath string, seedType string, seedPkgPath string) string { + cfg := packages.Config{ + Mode: packages.NeedModule | packages.LoadAllSyntax, + Dir: projectPath, + } + initial, err := packages.Load(&cfg, ".") + log.Printf("Got %d initial packages\n", len(initial)) + if err != nil { + log.Println(err) + } + + // Create SSA packages for well-typed packages and their dependencies. + prog, _ := ssautil.AllPackages(initial, 0) + + // Build SSA code for the whole program. + prog.Build() + + context := analysis.Context{ + Program: prog, + MainPackages: ssautil.MainPackages(prog.AllPackages()), + RootModule: initial[0].Module, + } + + valueFieldSetMap, frontierSet := analysis.GetValueToFieldMappingPass(context, prog, &seedType, &seedPkgPath) + fieldSets := []util.FieldSet{} + for v, path := range valueFieldSetMap { + if _, ok := frontierSet[v]; ok { + fieldSets = append(fieldSets, *path) + } + } + mergedFieldSet := util.MergeFieldSets(fieldSets...) + + taintAnalysisResult := TaintAnalysisResult{} + for _, field := range mergedFieldSet.Fields() { + taintAnalysisResult.UsedPaths = append(taintAnalysisResult.UsedPaths, field.Path) + } + taintedFieldSet := util.FieldSet{} + taintedSet := analysis.TaintAnalysisPass(context, prog, frontierSet, valueFieldSetMap) + for tainted := range taintedSet { + for _, path := range valueFieldSetMap[tainted].Fields() { + log.Printf("Path [%s] taints\n", path.Path) + taintedFieldSet.Add(&path) + } + log.Printf("value %s with path %s\n", tainted, valueFieldSetMap[tainted]) + // tainted.Parent().WriteTo(log.Writer()) + } + for _, field := range taintedFieldSet.Fields() { + taintAnalysisResult.TaintedPaths = append(taintAnalysisResult.TaintedPaths, field.Path) + } + + marshalled, _ := json.MarshalIndent(taintAnalysisResult, "", "\t") + return string(marshalled[:]) +} + func main() { // Load, parse, and type-check the whole program. @@ -94,31 +163,36 @@ func main() { // } log.Println("------------------------") + taintAnalysisResult := TaintAnalysisResult{} + for _, field := range mergedFieldSet.Fields() { + taintAnalysisResult.UsedPaths = append(taintAnalysisResult.UsedPaths, field.Path) + } + + taintedFieldSet := util.FieldSet{} taintedSet := analysis.TaintAnalysisPass(context, prog, frontierSet, valueFieldSetMap) for tainted := range taintedSet { for _, path := range valueFieldSetMap[tainted].Fields() { log.Printf("Path [%s] taints\n", path.Path) - mergedFieldSet.Delete(&path) + taintedFieldSet.Add(&path) } log.Printf("value %s with path %s\n", tainted, valueFieldSetMap[tainted]) // tainted.Parent().WriteTo(log.Writer()) } - - controlFlowResult := ControlFlowResult{} - for _, field := range mergedFieldSet.Fields() { - log.Printf("Path %s does not flow into k8s\n", field) - controlFlowResult.Paths = append(controlFlowResult.Paths, field.Path) + for _, field := range taintedFieldSet.Fields() { + taintAnalysisResult.TaintedPaths = append(taintAnalysisResult.TaintedPaths, field.Path) } + controlFlowResultFile, err := os.Create("controlFlowResult.json") if err != nil { log.Fatalf("Failed to create mapping.txt to write mapping: %v\n", err) } defer controlFlowResultFile.Close() - marshalled, _ := json.MarshalIndent(controlFlowResult, "", "\t") + marshalled, _ := json.MarshalIndent(taintAnalysisResult, "", "\t") controlFlowResultFile.Write(marshalled) } -type ControlFlowResult struct { - Paths [][]string `json:"paths"` +type TaintAnalysisResult struct { + UsedPaths [][]string `json:"usedPaths"` + TaintedPaths [][]string `json:"taintedPaths"` } diff --git a/ssa/util/util.go b/ssa/util/util.go index 037ed66dba..ce647ab845 100644 --- a/ssa/util/util.go +++ b/ssa/util/util.go @@ -4,7 +4,6 @@ import ( "fmt" "go/types" "log" - "os" "strconv" "strings" @@ -122,10 +121,6 @@ func FindSeedType(prog *ssa.Program, seedStr *string, pkgPath *string) *ssa.Type func FindSeedValues(prog *ssa.Program, seedType *string, pkgPath *string) []ssa.Value { seedVariables := []ssa.Value{} - seedOutFile, err := os.Create("seed.txt") - if err != nil { - log.Fatalf("Failed to create file %s\n", err) - } seed := FindSeedType(prog, seedType, pkgPath) if seed != nil { @@ -133,10 +128,10 @@ func FindSeedValues(prog *ssa.Program, seedType *string, pkgPath *string) []ssa. if seedStruct, ok := seed.Type().Underlying().(*types.Struct); ok { for i := 0; i < seedStruct.NumFields(); i++ { field := seedStruct.Field(i) - seedOutFile.WriteString(fmt.Sprintf("%s - %s\n", field.Name(), GetFieldNameFromJsonTag(seedStruct.Tag(i)))) + log.Printf("%s\n", fmt.Sprintf("%s - %s\n", field.Name(), GetFieldNameFromJsonTag(seedStruct.Tag(i)))) } } else { - seedOutFile.WriteString(fmt.Sprintf("%T", seed.Type().Underlying())) + log.Printf("%s\n", fmt.Sprintf("%T", seed.Type().Underlying())) } } @@ -152,9 +147,9 @@ func FindSeedValues(prog *ssa.Program, seedType *string, pkgPath *string) []ssa. } for _, seedVar := range seedVariables { - seedOutFile.WriteString(fmt.Sprintf("Value %s is seed\n", seedVar.String())) + log.Printf("%s\n", fmt.Sprintf("Value %s is seed\n", seedVar.String())) } - seedOutFile.WriteString(fmt.Sprintf("%d\n", len(seedVariables))) + log.Printf("%s\n", fmt.Sprintf("%d\n", len(seedVariables))) return seedVariables }