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 }