Skip to content

Commit 63ff0cb

Browse files
committed
template/text.Template execution methods: support reading arbitrary content
1 parent b9205b1 commit 63ff0cb

File tree

8 files changed

+171
-24
lines changed

8 files changed

+171
-24
lines changed

go/ql/lib/ext/text.template.model.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@ extensions:
77
- ["text/template", "", False, "HTMLEscapeString", "", "", "Argument[0]", "ReturnValue", "taint", "manual"]
88
- ["text/template", "", False, "JSEscape", "", "", "Argument[1]", "Argument[0]", "taint", "manual"]
99
- ["text/template", "", False, "JSEscapeString", "", "", "Argument[0]", "ReturnValue", "taint", "manual"]
10-
- ["text/template", "Template", True, "Execute", "", "", "Argument[1]", "Argument[0]", "taint", "manual"]
11-
- ["text/template", "Template", True, "ExecuteTemplate", "", "", "Argument[2]", "Argument[0]", "taint", "manual"]
10+
# - ["text/template", "Template", True, "Execute", "", "", "Argument[1]", "Argument[0]", "taint", "manual"] # Implemented in QL to provide an arbitrary content read from the input.
11+
# - ["text/template", "Template", True, "ExecuteTemplate", "", "", "Argument[2]", "Argument[0]", "taint", "manual"] # Implemented in QL to provide an arbitrary content read from the input.

go/ql/lib/semmle/go/dataflow/internal/DataFlowPrivate.qll

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,53 @@ predicate storeStep(Node node1, ContentSet c, Node node2) {
165165
containerStoreStep(node1, node2, c)
166166
}
167167

168+
/**
169+
* Gets a `DataFlow::ContentSet` containing a single `Content` appropriate
170+
* for reading a field, element, map value or channel message of type `containerType`.
171+
*/
172+
DataFlow::ContentSet getContentForType(Type containerType) {
173+
containerType instanceof ArrayType and
174+
result instanceof DataFlow::ArrayContent
175+
or
176+
containerType instanceof SliceType and
177+
result instanceof DataFlow::ArrayContent
178+
or
179+
containerType instanceof ChanType and
180+
result instanceof DataFlow::CollectionContent
181+
or
182+
containerType instanceof MapType and
183+
result instanceof DataFlow::MapValueContent
184+
or
185+
result.(DataFlow::PointerContent).getPointerType() = containerType
186+
or
187+
exists(Field f | f = containerType.(StructType).getField(_) |
188+
result.(DataFlow::FieldContent).getField() = f
189+
)
190+
}
191+
192+
/**
193+
* Gets the type of an array/slice element, channel value, map value,
194+
* pointer base type or named-type underlying type relating to `containerType`.
195+
*/
196+
Type getElementType(Type containerType) {
197+
result = containerType.(ArrayType).getElementType() or
198+
result = containerType.(SliceType).getElementType() or
199+
result = containerType.(ChanType).getElementType() or
200+
result = containerType.(MapType).getValueType() or
201+
result = containerType.(PointerType).getPointerType() or
202+
result = containerType.(NamedType).getUnderlyingType()
203+
}
204+
205+
/**
206+
* Gets the type of an array/slice element, channel value, map value,
207+
* pointer base type, named-type underlying type or struct field type
208+
* relating to `containerType`.
209+
*/
210+
Type getAnElementOrFieldType(Type containerType) {
211+
result = getElementType(containerType) or
212+
result = containerType.(StructType).getField(_).getType()
213+
}
214+
168215
/**
169216
* Holds if data can flow from `node1` to `node2` via a read of `c`.
170217
* Thus, `node1` references an object with a content `c` whose value ends up in
@@ -184,6 +231,14 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
184231
node2.(FlowSummaryNode).getSummaryNode())
185232
or
186233
containerReadStep(node1, node2, c)
234+
or
235+
exists(Type containerType |
236+
any(ImplicitFieldReadNode ifrn).shouldImplicitlyReadAllFields(node1) and
237+
getAnElementOrFieldType*(node1.getType()) = containerType
238+
|
239+
c = getContentForType(containerType) and
240+
node1 = node2
241+
)
187242
}
188243

189244
/**

go/ql/lib/semmle/go/dataflow/internal/DataFlowUtil.qll

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ private import semmle.go.dataflow.FunctionInputsAndOutputs
77
private import semmle.go.dataflow.ExternalFlow
88
private import DataFlowPrivate
99
private import FlowSummaryImpl as FlowSummaryImpl
10+
private import codeql.util.Unit
1011
import DataFlowNodes::Public
1112

1213
/**
@@ -50,6 +51,18 @@ abstract class FunctionModel extends Function {
5051
}
5152
}
5253

54+
/**
55+
* A unit class for adding nodes that should implicitly read from all nested content.
56+
*
57+
* For example, this might be appopriate for the argument to a method that serializes a struct.
58+
*/
59+
class ImplicitFieldReadNode extends Unit {
60+
/**
61+
* Holds if the node `n` should implicitly read from all nested content in a taint-tracking context.
62+
*/
63+
abstract predicate shouldImplicitlyReadAllFields(DataFlow::Node n);
64+
}
65+
5366
/**
5467
* Gets the `Node` corresponding to `insn`.
5568
*/

go/ql/lib/semmle/go/dataflow/internal/TaintTrackingUtil.qll

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,6 @@ predicate localTaintStep(DataFlow::Node src, DataFlow::Node sink) {
3434
FlowSummaryImpl::Private::Steps::summaryThroughStepTaint(src, sink, _)
3535
}
3636

37-
private Type getElementType(Type containerType) {
38-
result = containerType.(ArrayType).getElementType() or
39-
result = containerType.(SliceType).getElementType() or
40-
result = containerType.(ChanType).getElementType() or
41-
result = containerType.(MapType).getValueType() or
42-
result = containerType.(PointerType).getPointerType()
43-
}
44-
4537
/**
4638
* Holds if default `TaintTracking::Configuration`s should allow implicit reads
4739
* of `c` at sinks and inputs to additional taint steps.
@@ -50,21 +42,9 @@ bindingset[node]
5042
predicate defaultImplicitTaintRead(DataFlow::Node node, DataFlow::ContentSet c) {
5143
exists(Type containerType |
5244
node instanceof DataFlow::ArgumentNode and
53-
getElementType*(node.getType()) = containerType
45+
DataFlowPrivate::getElementType*(node.getType()) = containerType
5446
|
55-
containerType instanceof ArrayType and
56-
c instanceof DataFlow::ArrayContent
57-
or
58-
containerType instanceof SliceType and
59-
c instanceof DataFlow::ArrayContent
60-
or
61-
containerType instanceof ChanType and
62-
c instanceof DataFlow::CollectionContent
63-
or
64-
containerType instanceof MapType and
65-
c instanceof DataFlow::MapValueContent
66-
or
67-
c.(DataFlow::PointerContent).getPointerType() = containerType
47+
c = DataFlowPrivate::getContentForType(containerType)
6848
)
6949
}
7050

go/ql/lib/semmle/go/frameworks/stdlib/TextTemplate.qll

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,44 @@ module TextTemplate {
6767
input = inp and output = outp
6868
}
6969
}
70+
71+
private class ExecuteTemplateMethod extends Method {
72+
string name;
73+
int inputArg;
74+
75+
ExecuteTemplateMethod() {
76+
this.hasQualifiedName("text/template", "Template", name) and
77+
(
78+
name = "Execute" and inputArg = 1
79+
or
80+
name = "ExecuteTemplate" and inputArg = 2
81+
)
82+
}
83+
84+
int getInputArgIdx() { result = inputArg }
85+
}
86+
87+
private class ExecuteTemplateFieldReader extends DataFlow::ImplicitFieldReadNode {
88+
override predicate shouldImplicitlyReadAllFields(DataFlow::Node n) {
89+
exists(ExecuteTemplateMethod m, DataFlow::MethodCallNode cn |
90+
cn.getACalleeIncludingExternals().asFunction() = m and
91+
n = cn.getArgument(m.getInputArgIdx())
92+
)
93+
}
94+
}
95+
96+
private class ExecuteTemplateFunctionModels extends TaintTracking::FunctionModel,
97+
ExecuteTemplateMethod
98+
{
99+
FunctionInput inp;
100+
FunctionOutput outp;
101+
102+
ExecuteTemplateFunctionModels() {
103+
inp.isParameter(this.getInputArgIdx()) and outp.isParameter(0)
104+
}
105+
106+
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
107+
input = inp and output = outp
108+
}
109+
}
70110
}

go/ql/test/library-tests/semmle/go/frameworks/serialization/test.expected

Whitespace-only changes.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import go
2+
import TestUtilities.InlineFlowTest
3+
4+
string getArgString(DataFlow::Node src, DataFlow::Node sink) {
5+
exists(sink) and
6+
result = src.(DataFlow::CallNode).getArgument(0).getExactValue()
7+
}
8+
9+
import TaintFlowTestArgString<DefaultFlowConfig, getArgString/2>
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package xyz
2+
3+
import (
4+
"bytes"
5+
"text/template"
6+
)
7+
8+
type Inner1 struct {
9+
Data string
10+
}
11+
12+
type Inner2 struct {
13+
Data string
14+
}
15+
16+
type Inner3 struct {
17+
Data string
18+
}
19+
20+
type Outer struct {
21+
SliceField []Inner1
22+
PtrField *Inner2
23+
MapField map[string]Inner3
24+
}
25+
26+
func source(n int) string { return "dummy" }
27+
func sink(arg any) {}
28+
29+
func test() {
30+
31+
source1 := source(1)
32+
source2 := source(2)
33+
source3 := source(3)
34+
35+
toSerialize := Outer{[]Inner1{{source1}}, &Inner2{source2}, map[string]Inner3{"key": {source3}}}
36+
buff1 := new(bytes.Buffer)
37+
buff2 := new(bytes.Buffer)
38+
bytes1 := make([]byte, 10)
39+
bytes2 := make([]byte, 10)
40+
41+
tmpl, _ := template.New("test").Parse("Template text goes here (irrelevant for test)")
42+
tmpl.ExecuteTemplate(buff1, "test", toSerialize)
43+
buff1.Read(bytes1)
44+
sink(bytes1) // $ hasTaintFlow=1 hasTaintFlow=2 hasTaintFlow=3
45+
46+
tmpl.Execute(buff2, toSerialize)
47+
buff2.Read(bytes2)
48+
sink(bytes2) // $ hasTaintFlow=1 hasTaintFlow=2 hasTaintFlow=3
49+
50+
}

0 commit comments

Comments
 (0)