Skip to content

Commit

Permalink
Use propagation in fieldpropagator (#248)
Browse files Browse the repository at this point in the history
* move propagation to its own package

* move sourcetype identification logic to sourcetype package

* use propagation in fieldpropagator
  • Loading branch information
mlevesquedion authored Dec 30, 2020
1 parent 0036349 commit 0616ff0
Show file tree
Hide file tree
Showing 14 changed files with 125 additions and 410 deletions.
26 changes: 0 additions & 26 deletions cmd/sourcetype/main.go

This file was deleted.

67 changes: 33 additions & 34 deletions internal/pkg/fieldpropagator/analyzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (

"github.com/google/go-flow-levee/internal/pkg/config"
"github.com/google/go-flow-levee/internal/pkg/fieldtags"
"github.com/google/go-flow-levee/internal/pkg/propagation"
"github.com/google/go-flow-levee/internal/pkg/utils"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/buildssa"
Expand Down Expand Up @@ -49,7 +50,7 @@ var Analyzer = &analysis.Analyzer{
Name: "fieldpropagator",
Doc: `This analyzer identifies field propagators.
A field propagator is a function that returns a source field.`,
A field propagator is a function that returns a value that is tainted by a source field.`,
Flags: config.FlagSet,
Run: run,
Requires: []*analysis.Analyzer{buildssa.Analyzer, fieldtags.Analyzer},
Expand Down Expand Up @@ -105,43 +106,41 @@ func methodValues(ssaProg *ssa.Program, t types.Type) []*ssa.Function {
}

func analyzeBlocks(pass *analysis.Pass, conf *config.Config, tf fieldtags.ResultType, meth *ssa.Function) {
// Function does not return anything
if res := meth.Signature.Results(); res == nil || (*res).Len() == 0 {
return
}
var propagations []propagation.Propagation

for _, b := range meth.Blocks {
if len(b.Instrs) == 0 {
continue
for _, instr := range b.Instrs {
var (
txType types.Type
field int
)
switch t := instr.(type) {
case *ssa.Field:
txType = t.X.Type()
field = t.Field
case *ssa.FieldAddr:
txType = t.X.Type()
field = t.Field
default:
continue
}
if conf.IsSourceField(utils.DecomposeField(txType, field)) || tf.IsSourceField(txType, field) {
propagations = append(propagations, propagation.Dfs(instr.(ssa.Node), conf, tf))
}
}
lastInstr := b.Instrs[len(b.Instrs)-1]
ret, ok := lastInstr.(*ssa.Return)
if !ok {
continue
}
analyzeResults(pass, conf, tf, meth, ret.Results)
}
}

func analyzeResults(pass *analysis.Pass, conf *config.Config, tf fieldtags.ResultType, meth *ssa.Function, results []ssa.Value) {
for _, r := range results {
fa, ok := fieldAddr(r)
if !ok {
continue
}

xt, field := fa.X.Type(), fa.Field
if conf.IsSourceField(utils.DecomposeField(xt, field)) || tf.IsSourceField(xt, field) {
pass.ExportObjectFact(meth.Object(), &isFieldPropagator{})
for _, b := range meth.Blocks {
for _, instr := range b.Instrs {
ret, ok := instr.(*ssa.Return)
if !ok {
continue
}
for _, prop := range propagations {
if prop.IsTainted(ret) {
pass.ExportObjectFact(meth.Object(), &isFieldPropagator{})
}
}
}
}
}

func fieldAddr(x ssa.Value) (*ssa.FieldAddr, bool) {
switch t := x.(type) {
case *ssa.FieldAddr:
return t, true
case *ssa.UnOp:
return fieldAddr(t.X)
}
return nil, false
}
4 changes: 4 additions & 0 deletions internal/pkg/fieldpropagator/testdata/src/source/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ func (s Source) DataDeref() string { // want DataDeref:"field propagator identif
return *s.dataPtr
}

func (s Source) ShowData() string { // want ShowData:"field propagator identified"
return "Data: " + s.data
}

var isAdmin bool

func (s Source) MaybeData() string { // want MaybeData:"field propagator identified"
Expand Down
2 changes: 1 addition & 1 deletion internal/pkg/levee/levee.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (

"github.com/google/go-flow-levee/internal/pkg/config"
"github.com/google/go-flow-levee/internal/pkg/fieldtags"
"github.com/google/go-flow-levee/internal/pkg/levee/propagation"
"github.com/google/go-flow-levee/internal/pkg/propagation"
"github.com/google/go-flow-levee/internal/pkg/utils"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/ssa"
Expand Down
4 changes: 4 additions & 0 deletions internal/pkg/levee/testdata/src/example.com/core/source.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ func (s Source) GetData() string {
return s.Data
}

func (s Source) ShowData() string {
return "Data: " + s.Data
}

func (s Source) Copy() (Source, error) {
return s, nil
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ import (

func TestFieldAccessors(s core.Source, ptr *core.Source) {
core.Sinkf("Data: %v", s.GetData()) // want "a source has reached a sink"
core.Sinkf(s.ShowData()) // want "a source has reached a sink"
core.Sinkf("ID: %v", s.GetID())

core.Sinkf("Data: %v", ptr.GetData()) // want "a source has reached a sink"
core.Sinkf(ptr.ShowData()) // want "a source has reached a sink"
core.Sinkf("ID: %v", ptr.GetID())
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
"github.com/google/go-flow-levee/internal/pkg/config"
"github.com/google/go-flow-levee/internal/pkg/fieldtags"
"github.com/google/go-flow-levee/internal/pkg/sanitizer"
"github.com/google/go-flow-levee/internal/pkg/source"
"github.com/google/go-flow-levee/internal/pkg/sourcetype"
"github.com/google/go-flow-levee/internal/pkg/utils"
"golang.org/x/tools/go/pointer"
"golang.org/x/tools/go/ssa"
Expand Down Expand Up @@ -156,7 +156,7 @@ func (prop *Propagation) visit(n ssa.Node, maxInstrReached map[*ssa.BasicBlock]i

// This is to avoid attaching calls where the source is the receiver, ex:
// core.Sinkf("Source id: %v", wrapper.Source.GetID())
if recv := t.Call.Signature().Recv(); recv != nil && source.IsSourceType(prop.config, prop.taggedFields, recv.Type()) {
if recv := t.Call.Signature().Recv(); recv != nil && sourcetype.IsSourceType(prop.config, prop.taggedFields, recv.Type()) {
return
}

Expand Down
63 changes: 9 additions & 54 deletions internal/pkg/source/source.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@
package source

import (
"fmt"
"go/token"
"go/types"

"github.com/google/go-flow-levee/internal/pkg/config"
"github.com/google/go-flow-levee/internal/pkg/fieldpropagator"
"github.com/google/go-flow-levee/internal/pkg/fieldtags"
"github.com/google/go-flow-levee/internal/pkg/sourcetype"
"github.com/google/go-flow-levee/internal/pkg/utils"
"golang.org/x/tools/go/analysis/passes/buildssa"
"golang.org/x/tools/go/ssa"
Expand Down Expand Up @@ -90,7 +90,7 @@ func identify(conf *config.Config, ssaInput *buildssa.SSA, taggedFields fieldtag
func sourcesFromParams(fn *ssa.Function, conf *config.Config, taggedFields fieldtags.ResultType) []*Source {
var sources []*Source
for _, p := range fn.Params {
if IsSourceType(conf, taggedFields, p.Type()) {
if sourcetype.IsSourceType(conf, taggedFields, p.Type()) {
sources = append(sources, New(p))
}
}
Expand All @@ -106,7 +106,7 @@ func sourcesFromClosures(fn *ssa.Function, conf *config.Config, taggedFields fie
for _, p := range fn.FreeVars {
switch t := p.Type().(type) {
case *types.Pointer:
if IsSourceType(conf, taggedFields, t) {
if sourcetype.IsSourceType(conf, taggedFields, t) {
sources = append(sources, New(p))
}
}
Expand Down Expand Up @@ -135,31 +135,31 @@ func isSourceNode(n ssa.Node, conf *config.Config, propagators fieldpropagator.R

// Values produced by sanitizers are not sources.
case *ssa.Alloc:
return !isProducedBySanitizer(v, conf) && IsSourceType(conf, taggedFields, n.(ssa.Value).Type())
return !isProducedBySanitizer(v, conf) && sourcetype.IsSourceType(conf, taggedFields, n.(ssa.Value).Type())

// Values produced by sanitizers are not sources.
// Values produced by field propagators are.
case *ssa.Call:
return !isProducedBySanitizer(v, conf) &&
(propagators.IsFieldPropagator(v) || IsSourceType(conf, taggedFields, n.(ssa.Value).Type()))
(propagators.IsFieldPropagator(v) || sourcetype.IsSourceType(conf, taggedFields, n.(ssa.Value).Type()))

// A type assertion can assert that an interface is of a source type.
// Only panicky type asserts will refer to the source Value.
// The typed value returned in (value, ok) type assertions are examined in the case for ssa.Extract instructions.
case *ssa.TypeAssert:
return !v.CommaOk && IsSourceType(conf, taggedFields, v.AssertedType)
return !v.CommaOk && sourcetype.IsSourceType(conf, taggedFields, v.AssertedType)

// An Extract is used to obtain a value from an instruction that returns multiple values.
// If the extracted value is a Pointer to a Source, it won't have an Alloc, so we need to
// identify the Source from the Extract.
case *ssa.Extract:
t := v.Tuple.Type().(*types.Tuple).At(v.Index).Type()
_, ok := t.(*types.Pointer)
return ok && IsSourceType(conf, taggedFields, t)
return ok && sourcetype.IsSourceType(conf, taggedFields, t)

// Unary operator <- can receive sources from a channel.
case *ssa.UnOp:
return v.Op == token.ARROW && IsSourceType(conf, taggedFields, n.(ssa.Value).Type())
return v.Op == token.ARROW && sourcetype.IsSourceType(conf, taggedFields, n.(ssa.Value).Type())

// Field access (Field, FieldAddr),
// collection access (Index, IndexAddr, Lookup),
Expand All @@ -168,55 +168,10 @@ func isSourceNode(n ssa.Node, conf *config.Config, propagators fieldpropagator.R
case *ssa.Field, *ssa.FieldAddr,
*ssa.Index, *ssa.IndexAddr, *ssa.Lookup,
*ssa.MakeMap, *ssa.MakeChan:
return IsSourceType(conf, taggedFields, n.(ssa.Value).Type())
return sourcetype.IsSourceType(conf, taggedFields, n.(ssa.Value).Type())
}
}

// IsSourceType determines whether a Type is a Source Type.
// A Source Type is either:
// - A Named Type that is classified as a Source
// - A composite type that contains a Source Type
// - A Struct Type that contains a tagged field
func IsSourceType(c *config.Config, tf fieldtags.ResultType, t types.Type) bool {
deref := utils.Dereference(t)
switch tt := deref.(type) {
case *types.Named:
return c.IsSourceType(utils.DecomposeType(tt)) || IsSourceType(c, tf, tt.Underlying())
case *types.Array:
return IsSourceType(c, tf, tt.Elem())
case *types.Slice:
return IsSourceType(c, tf, tt.Elem())
case *types.Chan:
return IsSourceType(c, tf, tt.Elem())
case *types.Map:
key := IsSourceType(c, tf, tt.Key())
elem := IsSourceType(c, tf, tt.Elem())
return key || elem
case *types.Struct:
return hasTaggedField(tf, tt)
case *types.Basic, *types.Tuple, *types.Interface, *types.Signature:
// These types do not currently represent possible source types
return false
case *types.Pointer:
// This should be unreachable due to the dereference above
return false
default:
// The above should be exhaustive. Reaching this default case is an error.
fmt.Printf("unexpected type received: %T %v; please report this issue\n", tt, tt)
return false
}
}

func hasTaggedField(taggedFields fieldtags.ResultType, s *types.Struct) bool {
for i := 0; i < s.NumFields(); i++ {
f := s.Field(i)
if taggedFields.IsSource(f) {
return true
}
}
return false
}

func isProducedBySanitizer(v ssa.Value, conf *config.Config) bool {
for _, instr := range *v.Referrers() {
store, ok := instr.(*ssa.Store)
Expand Down
Loading

0 comments on commit 0616ff0

Please sign in to comment.