Skip to content

Commit 411f3cd

Browse files
authored
Merge pull request #17701 from smowton/smowton/feature/read-fields-before-executetemplate
Go: `template/text.Template` execution methods: support reading arbitrary content
2 parents 4cf0c8f + 9504f36 commit 411f3cd

File tree

11 files changed

+234
-57
lines changed

11 files changed

+234
-57
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* By implementing `ImplicitFieldReadNode` it is now possible to declare a dataflow node that reads any content (fields, array members, map keys and values). For example, this is appropriate for modelling a serialization method that flattens a potentially deep data structure into a string or byte array.
5+
* The `Template.Execute[Template]` methods of the `text/template` package now correctly convey taint from any nested fields to their result. This may produce more results from any taint-tracking query when the `text/template` package is in use.

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

+2-2
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

+33-25
Original file line numberDiff line numberDiff line change
@@ -143,47 +143,55 @@ predicate jumpStep(Node n1, Node n2) {
143143
* Thus, `node2` references an object with a content `x` that contains the
144144
* value of `node1`.
145145
*/
146-
predicate storeStep(Node node1, ContentSet c, Node node2) {
147-
// a write `(*p).f = rhs` is modeled as two store steps: `rhs` is flows into field `f` of `(*p)`,
148-
// which in turn flows into the pointer content of `p`
149-
exists(Write w, Field f, DataFlow::Node base, DataFlow::Node rhs | w.writesField(base, f, rhs) |
150-
node1 = rhs and
151-
node2.(PostUpdateNode).getPreUpdateNode() = base and
152-
c = any(DataFlow::FieldContent fc | fc.getField() = f)
146+
predicate storeStep(Node node1, ContentSet cs, Node node2) {
147+
exists(Content c | cs.asOneContent() = c |
148+
// a write `(*p).f = rhs` is modeled as two store steps: `rhs` is flows into field `f` of `(*p)`,
149+
// which in turn flows into the pointer content of `p`
150+
exists(Write w, Field f, DataFlow::Node base, DataFlow::Node rhs | w.writesField(base, f, rhs) |
151+
node1 = rhs and
152+
node2.(PostUpdateNode).getPreUpdateNode() = base and
153+
c = any(DataFlow::FieldContent fc | fc.getField() = f)
154+
or
155+
node1 = base and
156+
node2.(PostUpdateNode).getPreUpdateNode() = node1.(PointerDereferenceNode).getOperand() and
157+
c = any(DataFlow::PointerContent pc | pc.getPointerType() = node2.getType())
158+
)
153159
or
154-
node1 = base and
155-
node2.(PostUpdateNode).getPreUpdateNode() = node1.(PointerDereferenceNode).getOperand() and
160+
node1 = node2.(AddressOperationNode).getOperand() and
156161
c = any(DataFlow::PointerContent pc | pc.getPointerType() = node2.getType())
162+
or
163+
containerStoreStep(node1, node2, c)
157164
)
158165
or
159-
node1 = node2.(AddressOperationNode).getOperand() and
160-
c = any(DataFlow::PointerContent pc | pc.getPointerType() = node2.getType())
161-
or
162-
FlowSummaryImpl::Private::Steps::summaryStoreStep(node1.(FlowSummaryNode).getSummaryNode(), c,
166+
FlowSummaryImpl::Private::Steps::summaryStoreStep(node1.(FlowSummaryNode).getSummaryNode(), cs,
163167
node2.(FlowSummaryNode).getSummaryNode())
164-
or
165-
containerStoreStep(node1, node2, c)
166168
}
167169

168170
/**
169171
* Holds if data can flow from `node1` to `node2` via a read of `c`.
170172
* Thus, `node1` references an object with a content `c` whose value ends up in
171173
* `node2`.
172174
*/
173-
predicate readStep(Node node1, ContentSet c, Node node2) {
174-
node1 = node2.(PointerDereferenceNode).getOperand() and
175-
c = any(DataFlow::PointerContent pc | pc.getPointerType() = node1.getType())
176-
or
177-
exists(FieldReadNode read |
178-
node2 = read and
179-
node1 = read.getBase() and
180-
c = any(DataFlow::FieldContent fc | fc.getField() = read.getField())
175+
predicate readStep(Node node1, ContentSet cs, Node node2) {
176+
exists(Content c | cs.asOneContent() = c |
177+
node1 = node2.(PointerDereferenceNode).getOperand() and
178+
c = any(DataFlow::PointerContent pc | pc.getPointerType() = node1.getType())
179+
or
180+
exists(FieldReadNode read |
181+
node2 = read and
182+
node1 = read.getBase() and
183+
c = any(DataFlow::FieldContent fc | fc.getField() = read.getField())
184+
)
185+
or
186+
containerReadStep(node1, node2, c)
181187
)
182188
or
183-
FlowSummaryImpl::Private::Steps::summaryReadStep(node1.(FlowSummaryNode).getSummaryNode(), c,
189+
FlowSummaryImpl::Private::Steps::summaryReadStep(node1.(FlowSummaryNode).getSummaryNode(), cs,
184190
node2.(FlowSummaryNode).getSummaryNode())
185191
or
186-
containerReadStep(node1, node2, c)
192+
any(ImplicitFieldReadNode ifrn).shouldImplicitlyReadAllFields(node1) and
193+
cs.isUniversalContent() and
194+
node1 = node2
187195
}
188196

189197
/**

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

+54-5
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 appropriate 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
*/
@@ -169,6 +182,11 @@ class Content extends TContent {
169182
) {
170183
filepath = "" and startline = 0 and startcolumn = 0 and endline = 0 and endcolumn = 0
171184
}
185+
186+
/**
187+
* Gets the `ContentSet` contaning only this content.
188+
*/
189+
ContentSet asContentSet() { result.asOneContent() = this }
172190
}
173191

174192
/** A reference through a field. */
@@ -236,21 +254,33 @@ class SyntheticFieldContent extends Content, TSyntheticFieldContent {
236254
override string toString() { result = s.toString() }
237255
}
238256

257+
private newtype TContentSet =
258+
TOneContent(Content c) or
259+
TAllContent()
260+
239261
/**
240262
* An entity that represents a set of `Content`s.
241263
*
242264
* The set may be interpreted differently depending on whether it is
243265
* stored into (`getAStoreContent`) or read from (`getAReadContent`).
244266
*/
245-
class ContentSet instanceof Content {
267+
class ContentSet instanceof TContentSet {
246268
/** Gets a content that may be stored into when storing into this set. */
247-
Content getAStoreContent() { result = this }
269+
Content getAStoreContent() { this = TOneContent(result) }
248270

249271
/** Gets a content that may be read from when reading from this set. */
250-
Content getAReadContent() { result = this }
272+
Content getAReadContent() {
273+
this = TOneContent(result)
274+
or
275+
this = TAllContent() and exists(result)
276+
}
251277

252278
/** Gets a textual representation of this content set. */
253-
string toString() { result = super.toString() }
279+
string toString() {
280+
exists(Content c | this = TOneContent(c) | result = c.toString())
281+
or
282+
this = TAllContent() and result = "all content"
283+
}
254284

255285
/**
256286
* Holds if this element is at the specified location.
@@ -262,8 +292,27 @@ class ContentSet instanceof Content {
262292
predicate hasLocationInfo(
263293
string filepath, int startline, int startcolumn, int endline, int endcolumn
264294
) {
265-
super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
295+
exists(Content c | this = TOneContent(c) |
296+
c.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
297+
)
298+
or
299+
this = TAllContent() and
300+
filepath = "" and
301+
startline = 0 and
302+
startcolumn = 0 and
303+
endline = 0 and
304+
endcolumn = 0
266305
}
306+
307+
/**
308+
* If this is a singleton content set, returns the content.
309+
*/
310+
Content asOneContent() { this = TOneContent(result) }
311+
312+
/**
313+
* Holds if this is a universal content set.
314+
*/
315+
predicate isUniversalContent() { this = TAllContent() }
267316
}
268317

269318
/**

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

+24-20
Original file line numberDiff line numberDiff line change
@@ -61,26 +61,28 @@ module Input implements InputSig<Location, DataFlowImplSpecific::GoDataFlow> {
6161
}
6262

6363
string encodeContent(ContentSet cs, string arg) {
64-
exists(Field f, string package, string className, string fieldName |
65-
f = cs.(FieldContent).getField() and
66-
f.hasQualifiedName(package, className, fieldName) and
67-
result = "Field" and
68-
arg = package + "." + className + "." + fieldName
69-
)
70-
or
71-
exists(SyntheticField f |
72-
f = cs.(SyntheticFieldContent).getField() and result = "SyntheticField" and arg = f
64+
exists(Content c | cs.asOneContent() = c |
65+
exists(Field f, string package, string className, string fieldName |
66+
f = c.(FieldContent).getField() and
67+
f.hasQualifiedName(package, className, fieldName) and
68+
result = "Field" and
69+
arg = package + "." + className + "." + fieldName
70+
)
71+
or
72+
exists(SyntheticField f |
73+
f = c.(SyntheticFieldContent).getField() and result = "SyntheticField" and arg = f
74+
)
75+
or
76+
c instanceof ArrayContent and result = "ArrayElement" and arg = ""
77+
or
78+
c instanceof CollectionContent and result = "Element" and arg = ""
79+
or
80+
c instanceof MapKeyContent and result = "MapKey" and arg = ""
81+
or
82+
c instanceof MapValueContent and result = "MapValue" and arg = ""
83+
or
84+
c instanceof PointerContent and result = "Dereference" and arg = ""
7385
)
74-
or
75-
cs instanceof ArrayContent and result = "ArrayElement" and arg = ""
76-
or
77-
cs instanceof CollectionContent and result = "Element" and arg = ""
78-
or
79-
cs instanceof MapKeyContent and result = "MapKey" and arg = ""
80-
or
81-
cs instanceof MapValueContent and result = "MapValue" and arg = ""
82-
or
83-
cs instanceof PointerContent and result = "Dereference" and arg = ""
8486
}
8587

8688
bindingset[token]
@@ -523,7 +525,9 @@ module Private {
523525
SummaryComponent qualifier() { result = argument(-1) }
524526

525527
/** Gets a summary component for field `f`. */
526-
SummaryComponent field(Field f) { result = content(any(FieldContent c | c.getField() = f)) }
528+
SummaryComponent field(Field f) {
529+
result = content(any(FieldContent c | c.getField() = f).asContentSet())
530+
}
527531

528532
/** Gets a summary component that represents the return value of a call. */
529533
SummaryComponent return() { result = SC::return(_) }

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

+5-4
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,11 @@ private Type getElementType(Type containerType) {
4747
* of `c` at sinks and inputs to additional taint steps.
4848
*/
4949
bindingset[node]
50-
predicate defaultImplicitTaintRead(DataFlow::Node node, DataFlow::ContentSet c) {
51-
exists(Type containerType |
50+
predicate defaultImplicitTaintRead(DataFlow::Node node, DataFlow::ContentSet cs) {
51+
exists(Type containerType, DataFlow::Content c |
5252
node instanceof DataFlow::ArgumentNode and
53-
getElementType*(node.getType()) = containerType
53+
getElementType*(node.getType()) = containerType and
54+
cs.asOneContent() = c
5455
|
5556
containerType instanceof ArrayType and
5657
c instanceof DataFlow::ArrayContent
@@ -142,7 +143,7 @@ predicate elementWriteStep(DataFlow::Node pred, DataFlow::Node succ) {
142143
any(DataFlow::Write w).writesElement(succ.(DataFlow::PostUpdateNode).getPreUpdateNode(), _, pred)
143144
or
144145
FlowSummaryImpl::Private::Steps::summaryStoreStep(pred.(DataFlowPrivate::FlowSummaryNode)
145-
.getSummaryNode(), any(DataFlow::Content c | c instanceof DataFlow::ArrayContent),
146+
.getSummaryNode(), any(DataFlow::ArrayContent ac).asContentSet(),
146147
succ.(DataFlowPrivate::FlowSummaryNode).getSummaryNode())
147148
}
148149

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ module NetHttp {
122122
|
123123
lastParamIndex = call.getCall().getCalleeType().getNumParameter() - 1 and
124124
varArgsSliceArgument = SummaryComponentStack::argument(lastParamIndex) and
125-
arrayContentSC = SummaryComponent::content(arrayContent) and
125+
arrayContentSC = SummaryComponent::content(arrayContent.asContentSet()) and
126126
stack = SummaryComponentStack::push(arrayContentSC, varArgsSliceArgument)
127127
)
128128
else stack = SummaryComponentStack::argument(n)

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

+41
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,45 @@ module TextTemplate {
6767
input = inp and output = outp
6868
}
6969
}
70+
71+
private class ExecuteTemplateMethod extends Method {
72+
int inputArg;
73+
74+
ExecuteTemplateMethod() {
75+
exists(string name |
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+
85+
int getInputArgIdx() { result = inputArg }
86+
}
87+
88+
private class ExecuteTemplateFieldReader extends DataFlow::ImplicitFieldReadNode {
89+
override predicate shouldImplicitlyReadAllFields(DataFlow::Node n) {
90+
exists(ExecuteTemplateMethod m, DataFlow::MethodCallNode cn |
91+
cn.getTarget() = m and
92+
n = cn.getArgument(m.getInputArgIdx())
93+
)
94+
}
95+
}
96+
97+
private class ExecuteTemplateFunctionModels extends TaintTracking::FunctionModel,
98+
ExecuteTemplateMethod
99+
{
100+
FunctionInput inp;
101+
FunctionOutput outp;
102+
103+
ExecuteTemplateFunctionModels() {
104+
inp.isParameter(this.getInputArgIdx()) and outp.isParameter(0)
105+
}
106+
107+
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
108+
input = inp and output = outp
109+
}
110+
}
70111
}

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

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import go
2+
import utils.test.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>

0 commit comments

Comments
 (0)