github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/transform/staticlark/dataflow_test.go (about)

     1  package staticlark
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/google/go-cmp/cmp"
     7  	"github.com/google/go-cmp/cmp/cmpopts"
     8  	"go.starlark.net/starlark"
     9  	"go.starlark.net/syntax"
    10  )
    11  
    12  func TestSingleFunctionDataflow(t *testing.T) {
    13  	filename := "testdata/safety_funcs.star"
    14  	callGraph := mustBuildCallGraphFromFile(t, filename)
    15  
    16  	// axioms: get_secret returns a sensitive value, and dangerous does
    17  	// something unsafe with its first parameter
    18  	axioms := map[string]*funcNode{
    19  		"get_secret": &funcNode{
    20  			name:            "get_secret",
    21  			sensitiveReturn: true,
    22  		},
    23  		"dangerous": &funcNode{
    24  			dangerousParams: []bool{true, false},
    25  			reasonParams: []reason{
    26  				reason{lines: []string{"assume it is dangerous"}},
    27  				reason{},
    28  			},
    29  		},
    30  	}
    31  
    32  	diags, err := analyzeSensitiveDataflow(callGraph, axioms)
    33  	if err != nil {
    34  		t.Error(err)
    35  	}
    36  
    37  	expectDiags := []Diagnostic{
    38  		Diagnostic{
    39  			Pos:      syntax.MakePosition(&filename, 22, 3),
    40  			Category: "leak",
    41  			Message:  "secrets may leak, variable c is secret\nassume it is dangerous",
    42  		},
    43  	}
    44  
    45  	ignoreCmp := cmpopts.IgnoreUnexported(syntax.Position{})
    46  	if diff := cmp.Diff(expectDiags, diags, ignoreCmp); diff != "" {
    47  		t.Errorf("mismatch (-want +got):\n%s", diff)
    48  	}
    49  
    50  	fn := callGraph.lookup["processor"]
    51  	if !fn.sensitiveReturn {
    52  		t.Errorf("expected: function `processor` returns a sensitive value")
    53  	}
    54  }
    55  
    56  func TestCallTraceDataflow(t *testing.T) {
    57  	filename := "testdata/call_trace.star"
    58  	callGraph := mustBuildCallGraphFromFile(t, filename)
    59  
    60  	// axioms: get_secret returns a sensitive value, and dangerous does
    61  	// something unsafe with its first parameter
    62  	axioms := map[string]*funcNode{
    63  		"get_secret": &funcNode{
    64  			name:            "get_secret",
    65  			sensitiveReturn: true,
    66  		},
    67  		"dangerous": &funcNode{
    68  			dangerousParams: []bool{true, false},
    69  			reasonParams: []reason{
    70  				reason{lines: []string{"assume it is dangerous"}},
    71  				reason{},
    72  			},
    73  		},
    74  	}
    75  
    76  	diags, err := analyzeSensitiveDataflow(callGraph, axioms)
    77  	if err != nil {
    78  		t.Error(err)
    79  	}
    80  
    81  	expectDiags := []Diagnostic{
    82  		Diagnostic{
    83  			Pos:      syntax.MakePosition(&filename, 33, 3),
    84  			Category: "leak",
    85  			Message: `secrets may leak, variable i is secret
    86  call_trace.star:26: middle passes f to bottom argument m
    87  call_trace.star:17: bottom passes b to dangerous argument s
    88  assume it is dangerous`,
    89  		},
    90  		Diagnostic{
    91  			Pos:      syntax.MakePosition(&filename, 33, 3),
    92  			Category: "leak",
    93  			Message: `secrets may leak, variable k is secret
    94  call_trace.star:26: middle passes g to bottom argument n
    95  call_trace.star:18: bottom passes n to dangerous argument s
    96  assume it is dangerous`,
    97  		},
    98  	}
    99  
   100  	ignoreCmp := cmpopts.IgnoreUnexported(syntax.Position{})
   101  	if diff := cmp.Diff(expectDiags, diags, ignoreCmp); diff != "" {
   102  		t.Errorf("mismatch (-want +got):\n%s", diff)
   103  	}
   104  }
   105  
   106  func TestIfStatementDataflow(t *testing.T) {
   107  	filename := "testdata/control_funcs.star"
   108  	callGraph := mustBuildCallGraphFromFile(t, filename)
   109  
   110  	// axioms: get_secret returns a sensitive value, and dangerous does
   111  	// something unsafe with its first parameter
   112  	axioms := map[string]*funcNode{
   113  		"get_secret": &funcNode{
   114  			name:            "get_secret",
   115  			sensitiveReturn: true,
   116  		},
   117  		"dangerous": &funcNode{
   118  			dangerousParams: []bool{true, false},
   119  			reasonParams: []reason{
   120  				reason{lines: []string{"assume it is dangerous"}},
   121  				reason{},
   122  			},
   123  		},
   124  	}
   125  
   126  	diags, err := analyzeSensitiveDataflow(callGraph, axioms)
   127  	if err != nil {
   128  		t.Error(err)
   129  	}
   130  
   131  	expectDiags := []Diagnostic{
   132  		Diagnostic{
   133  			Pos:      syntax.MakePosition(&filename, 33, 3),
   134  			Category: "leak",
   135  			Message: `secrets may leak, variable z is secret
   136  control_funcs.star:27: something passes m to dangerous argument s
   137  assume it is dangerous`,
   138  		},
   139  	}
   140  
   141  	// TODO(dustmop): Add this to the reason
   142  	// control_funcs.star:17: m is influenced by c due to control flow
   143  
   144  	ignoreCmp := cmpopts.IgnoreUnexported(syntax.Position{})
   145  	if diff := cmp.Diff(expectDiags, diags, ignoreCmp); diff != "" {
   146  		t.Errorf("mismatch (-want +got):\n%s", diff)
   147  	}
   148  }
   149  
   150  func mustBuildCallGraphFromFile(t *testing.T, filename string) *callGraph {
   151  	f, err := syntax.Parse(filename, nil, 0)
   152  	if err != nil {
   153  		t.Fatal(err)
   154  	}
   155  	funcs, topLevel, err := collectFuncDefsTopLevelCalls(f.Stmts)
   156  	if err != nil {
   157  		t.Fatal(err)
   158  	}
   159  	globals := newSymtable(starlark.Universe)
   160  	return buildCallGraph(funcs, topLevel, globals)
   161  }