Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: google/go-flow-levee
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v0.1.6
Choose a base ref
...
head repository: google/go-flow-levee
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref
  • 2 commits
  • 16 files changed
  • 2 contributors

Commits on Jul 15, 2021

  1. Copy the full SHA
    c0094bf View commit details

Commits on Aug 9, 2022

  1. Update to dependency on golang.org/x/tools to v0.1.12. (#327)

    * Update to dependency on golang.org/x/tools to v0.1.12.
    
    This prevents crashes on Go code containing generics. The standard library now contains generics at head and levee crashes for folks using newer toolchains.
    
    See kubernetes/kubernetes#111452.
    
    * Update GH workflow to use go 1.18.
    timothy-king authored Aug 9, 2022
    Copy the full SHA
    f056bc4 View commit details
6 changes: 3 additions & 3 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@ on:
branches: [master]

env:
GO_VERSION: "1.14"
GO_VERSION: "1.18"

jobs:
lint:
@@ -47,13 +47,13 @@ jobs:
- name: Check for license headers
if: ${{ always() }}
run: |
go get -u github.com/google/addlicense
go install github.com/google/addlicense@latest
addlicense -check cmd internal pkg
- name: Run staticcheck
if: ${{ always() }}
run: |
go install honnef.co/go/tools/cmd/staticcheck
go install honnef.co/go/tools/cmd/staticcheck@v0.3.3
staticcheck ./...
- name: Check YAML
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -4,6 +4,6 @@ go 1.14

require (
github.com/google/go-cmp v0.5.2
golang.org/x/tools v0.0.0-20200416214402-fc959738d646
golang.org/x/tools v0.1.12
sigs.k8s.io/yaml v1.2.0
)
30 changes: 19 additions & 11 deletions go.sum
Original file line number Diff line number Diff line change
@@ -2,24 +2,32 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200416214402-fc959738d646 h1:7CEkhBsBejkW845gR1AmglqMfc1yGzn42FBmtM4jxyM=
golang.org/x/tools v0.0.0-20200416214402-fc959738d646/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
6 changes: 6 additions & 0 deletions internal/pkg/debug/render/render_test.go
Original file line number Diff line number Diff line change
@@ -26,6 +26,7 @@ import (
"testing"

"github.com/google/go-cmp/cmp"
"github.com/google/go-flow-levee/internal/pkg/utils"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil"
)
@@ -54,6 +55,11 @@ func testGoldenFiles(t *testing.T, fn func(f *ssa.Function) string, fnName, ext
}
want := string(bytes)

// Replace interface{} in golden files if necessary.
if utils.DefaultEmptyInterface != "interface{}" {
want = strings.ReplaceAll(want, "interface{}", utils.DefaultEmptyInterface)
}

got := fn(f)

if diff := cmp.Diff(want, got); diff != "" {
4 changes: 3 additions & 1 deletion internal/pkg/earpointer/analysis.go
Original file line number Diff line number Diff line change
@@ -100,7 +100,9 @@ func analyze(ssainput *buildssa.SSA) *Partitions {
}
}
}
return vis.state.ToPartitions()
p := vis.state.ToPartitions()
p.cg = cg
return p
}

// Builds the calling context set for each function.
4 changes: 2 additions & 2 deletions internal/pkg/earpointer/analysis_test.go
Original file line number Diff line number Diff line change
@@ -1201,11 +1201,11 @@ func TestMethodInvoke(t *testing.T) {
// Note that in "**T2", the first "*" is the synthesized ValueOf operator,
// and "*T2" is the receiver type.
want := concat(map[string]string{
"{**T2:f.arg0}": "[T1->*T1:f.arg0]",
"{**T2:f.arg0}": "[T1->g.t0]",
"{*T1:f.arg0,*T2:f.t0,g.t0}": "[]",
"{*T1:f.x,*T2:f.x,g.x}": "[]",
"{*T2:f.arg0}": "--> **T2:f.arg0",
"{*g.x2}": "[T1->*T1:f.arg0]",
"{*g.x2}": "[T1->g.t0]",
"{g.x2}": "--> *g.x2",
})
if got := state.String(); got != want {
5 changes: 5 additions & 0 deletions internal/pkg/earpointer/state.go
Original file line number Diff line number Diff line change
@@ -22,6 +22,8 @@ import (
"log"
"sort"
"strings"

"golang.org/x/tools/go/callgraph"
)

// parentMap maps a reference to its representative (i.e. the parent
@@ -296,6 +298,9 @@ type Partitions struct {
// It is constructed separately using the "ConstructFieldParentMap()"
// at the final phase.
revFields map[Reference][]Reference

// The call graph used to unify callers and callees.
cg *callgraph.Graph
}

func (state *state) ToPartitions() *Partitions {
114 changes: 66 additions & 48 deletions internal/pkg/earpointer/taint.go
Original file line number Diff line number Diff line change
@@ -17,6 +17,8 @@ package earpointer
import (
"go/types"

"golang.org/x/tools/go/callgraph"

"github.com/google/go-flow-levee/internal/pkg/config"
"github.com/google/go-flow-levee/internal/pkg/utils"

@@ -26,15 +28,17 @@ import (

// Bounded traversal of an EAR heap.
type heapTraversal struct {
heap *Partitions
callees map[*ssa.Function]bool // the functions containing the references of interest
visited ReferenceSet // the visited references during the traversal
heap *Partitions
// The reachable functions containing the references of interest.
reachableFns map[*ssa.Function]bool
// The visited references during the traversal.
visited ReferenceSet
isTaintField func(named *types.Named, index int) bool
}

func (ht *heapTraversal) isWithinCallees(ref Reference) bool {
if fn := ref.Value().Parent(); fn != nil {
return ht.callees[fn]
return ht.reachableFns[fn]
}
// Globals and Builtins have no parents.
return true
@@ -59,7 +63,8 @@ func (ht *heapTraversal) srcRefs(rep Reference, tp types.Type, result ReferenceS
ht.srcRefs(rep, tp.Underlying(), result)
return
}
result[rep] = true // the current struct object is tainted
// Mark the current struct object as tainted.
result[rep] = true
// Look for the taint fields.
for i := 0; i < tt.NumFields(); i++ {
f := tt.Field(i)
@@ -132,7 +137,7 @@ func (ht *heapTraversal) canReach(sink ssa.Instruction, sources []*source.Source
sinkedRefs := make(map[Reference]bool)
for _, op := range sink.Operands(nil) {
// Use a separate heapTraversal to search for the sink references.
sinkHT := &heapTraversal{heap: ht.heap, callees: ht.callees, visited: make(ReferenceSet)}
sinkHT := &heapTraversal{heap: ht.heap, reachableFns: ht.reachableFns, visited: make(ReferenceSet)}
v := *op
if isLocal(v) || isGlobal(v) {
ref := MakeLocalWithEmptyContext(v)
@@ -153,47 +158,45 @@ func (ht *heapTraversal) canReach(sink ssa.Instruction, sources []*source.Source
return nil
}

// For a function, transitively get the functions called within this function.
// Argument "depth" controls the depth of the call chain.
// For example, return {g1,g2,g3} for "func f(){ g1(); g2() }, func g1(){ g3() }".
func calleeFunctions(fn *ssa.Function, result map[*ssa.Function]bool, depth uint) {
if depth <= 0 {
// For a function, transitively get the functions reachable from this function
// according to the call graph. Both callers and callees are considered.
// Argument "depth" controls the depth of the call chain, and "result" is
// to store the set of reachable functions. For example,
// func f(){ g1(); g2() }
// func g1(){ g3() }
// for input "g1", result = {f,g1,g2,g3} if depth>1, and result = {f, g1} if depth=1.
func boundedReachableFunctions(fn *ssa.Function, cg *callgraph.Graph, depth uint, result map[*ssa.Function]bool) {
if depth <= 0 || result[fn] {
return
}
for _, b := range fn.Blocks {
for _, instr := range b.Instrs {
if call, ok := instr.(*ssa.Call); ok {
// TODO(#317): use more advanced call graph.
// skip empty, unlinked, or visited functions
if callee := call.Call.StaticCallee(); callee != nil && len(callee.Blocks) > 0 && !result[callee] {
result[callee] = true
calleeFunctions(callee, result, depth-1)
}
}
}
}
}

func boundedDepthCallees(fn *ssa.Function, depth uint) map[*ssa.Function]bool {
result := make(map[*ssa.Function]bool)
result[fn] = true
calleeFunctions(fn, result, depth)
return result
node := cg.Nodes[fn]
if node == nil {
return
}
// Visit the callees within "fn".
for _, out := range node.Out {
boundedReachableFunctions(out.Callee.Func, cg, depth-1, result)
}
// Visit the callers of "fn".
for _, in := range node.In {
boundedReachableFunctions(in.Caller.Func, cg, depth-1, result)
}
}

// Obtain the references which are aliases of a taint source, with field sensitivity.
// Argument "heap" is an immutable EAR heap containing alias information;
// "callees" is used to bound the searching of source references in the heap.
// "reachable" is used to bound the searching of source references in the heap.
func srcAliasRefs(src *source.Source, isTaintField func(named *types.Named, index int) bool,
heap *Partitions, callees map[*ssa.Function]bool) ReferenceSet {
heap *Partitions, reachable map[*ssa.Function]bool) ReferenceSet {

val, ok := src.Node.(ssa.Value)
if !ok {
return nil
}
rep := heap.Representative(MakeLocalWithEmptyContext(val))
refs := make(ReferenceSet)
ht := &heapTraversal{heap: heap, callees: callees, visited: make(ReferenceSet), isTaintField: isTaintField}
ht := &heapTraversal{heap: heap, reachableFns: reachable, visited: make(ReferenceSet), isTaintField: isTaintField}
ht.srcRefs(rep, val.Type(), refs)
return refs
}
@@ -206,33 +209,49 @@ type SourceSinkTrace struct {

// Look for <source, sink> pairs by examining the heap alias information.
func SourcesToSinks(funcSources source.ResultType, isTaintField func(named *types.Named, index int) bool,
heap *Partitions, conf *config.Config) []*SourceSinkTrace {
heap *Partitions, conf *config.Config) map[ssa.Instruction]*SourceSinkTrace {

var traces []*SourceSinkTrace
// A map from a callsite to its possible callees.
calleeMap := mapCallees(heap.cg)
traces := make(map[ssa.Instruction]*SourceSinkTrace)
for fn, sources := range funcSources {
// Transitively get the set of functions called within "fn".
// Transitively get the set of functions reachable from "fn".
// This set is used to narrow down the set of references needed to be
// considered during EAR heap traversal. It can also help reducing the
// false positives and boosting the performance.
callees := boundedDepthCallees(fn, conf.EARTaintCallSpan)
// For example,
// func f1(){ f2(); g4() }
// func f2(){ g1(); g2() }
// func g1(){ g3() }
// g1 can reach f2 through the caller, and then f1 similarly,
// and then g4 through the callee.
// g1's full reachable set is {f1,f2,g1,g2,g3,g4}.
reachable := make(map[*ssa.Function]bool)
boundedReachableFunctions(fn, heap.cg, conf.EARTaintCallSpan, reachable)
// Start from the set of taint sources.
srcRefs := make(map[*source.Source]ReferenceSet)
for _, s := range sources {
srcRefs[s] = srcAliasRefs(s, isTaintField, heap, callees)
srcRefs[s] = srcAliasRefs(s, isTaintField, heap, reachable)
}
// Traverse all the callee functions (not just the ones with sink sources)
ht := &heapTraversal{heap: heap, callees: callees, visited: make(ReferenceSet)}
for member := range callees {
// Traverse all the reachable functions (not just the ones with sink sources)
// in search for connected sinks.
ht := &heapTraversal{heap: heap, reachableFns: reachable, visited: make(ReferenceSet)}
for member := range reachable {
for _, b := range member.Blocks {
for _, instr := range b.Instrs {
switch v := instr.(type) {
case *ssa.Call:
callees := calleeMap[&v.Call]
sink := instr
// TODO(#317): use more advanced call graph.
callee := v.Call.StaticCallee()
if callee != nil && conf.IsSink(utils.DecomposeFunction(callee)) {
if src := ht.canReach(sink, sources, srcRefs); src != nil {
traces = append(traces, &SourceSinkTrace{Src: src, Sink: sink})
break
for _, callee := range callees {
if conf.IsSink(utils.DecomposeFunction(callee)) {
if src := ht.canReach(sink, sources, srcRefs); src != nil {
// If a previous source has been found, be in favor of the source within the same
// function. This can be extended to be in favor of the source closest to the sink.
if _, ok := traces[instr]; !ok || src.Node.Parent() == sink.Parent() {
traces[sink] = &SourceSinkTrace{Src: src, Sink: sink}
}
}
}
}
case *ssa.Panic:
@@ -241,8 +260,7 @@ func SourcesToSinks(funcSources source.ResultType, isTaintField func(named *type
}
sink := instr
if src := ht.canReach(sink, sources, srcRefs); src != nil {
traces = append(traces, &SourceSinkTrace{Src: src, Sink: sink})
break
traces[sink] = &SourceSinkTrace{Src: src, Sink: sink}
}

}
9 changes: 9 additions & 0 deletions internal/pkg/levee/levee_ear_test.go
Original file line number Diff line number Diff line change
@@ -65,3 +65,12 @@ func TestLeveeEAR(t *testing.T) {
// analysistest.Run(t, dataDir, Analyzer, "./src/levee_analysistest/example/tests/structlit") // TODO: NP have been fixed?
analysistest.Run(t, dataDir, Analyzer, "./src/levee_analysistest/example/tests/typealias")
}

func TestLeveeEARInter(t *testing.T) {
dataDir := analysistest.TestData()
if err := Analyzer.Flags.Set("config", dataDir+"/test-ear-config.yaml"); err != nil {
t.Error(err)
}

analysistest.Run(t, dataDir, Analyzer, "./src/levee_analysistest/ear/tests/...")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package call

import (
"levee_analysistest/example/core"
)

func sinkf2(i interface{}) {
core.Sink(i) // want "a source has reached a sink"
}

func createSource2() interface{} {
return &core.Source{}
}

func identity(arg interface{}) interface{} {
return arg
}

// Test the case where:
// (1) the sink function doesn't embed the source function; and
// (2) the source function doesn't embed the sink function.
func TestSrcSinkInDifferentCallees() {
// The source is created in a callee.
s := createSource2()
i := identity(s)
// The sink is within another callee.
sinkf2(i)
}
Loading