Skip to content

Commit c0cbb4a

Browse files
committed
template/text.Template execution methods: support reading arbitrary content
1 parent c629867 commit c0cbb4a

File tree

8 files changed

+169
-16
lines changed

8 files changed

+169
-16
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/TaintTrackingUtil.qll

Lines changed: 58 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,33 @@ private Type getElementType(Type containerType) {
3939
result = containerType.(SliceType).getElementType() or
4040
result = containerType.(ChanType).getElementType() or
4141
result = containerType.(MapType).getValueType() or
42-
result = containerType.(PointerType).getPointerType()
42+
result = containerType.(PointerType).getPointerType() or
43+
result = containerType.(NamedType).getUnderlyingType()
44+
}
45+
46+
private Type getElementTypeIncludingFields(Type containerType) {
47+
result = getElementType(containerType) or
48+
result = containerType.(StructType).getField(_).getType()
49+
}
50+
51+
private DataFlow::ContentSet getContentForType(Type containerType) {
52+
containerType instanceof ArrayType and
53+
result instanceof DataFlow::ArrayContent
54+
or
55+
containerType instanceof SliceType and
56+
result instanceof DataFlow::ArrayContent
57+
or
58+
containerType instanceof ChanType and
59+
result instanceof DataFlow::CollectionContent
60+
or
61+
containerType instanceof MapType and
62+
result instanceof DataFlow::MapValueContent
63+
or
64+
result.(DataFlow::PointerContent).getPointerType() = containerType
65+
or
66+
exists(Field f | f = containerType.(StructType).getField(_) |
67+
result.(DataFlow::FieldContent).getField() = f
68+
)
4369
}
4470

4571
/**
@@ -51,23 +77,41 @@ predicate defaultImplicitTaintRead(DataFlow::Node node, DataFlow::ContentSet c)
5177
exists(Type containerType |
5278
node instanceof DataFlow::ArgumentNode and
5379
getElementType*(node.getType()) = containerType
54-
|
55-
containerType instanceof ArrayType and
56-
c instanceof DataFlow::ArrayContent
57-
or
58-
containerType instanceof SliceType and
59-
c instanceof DataFlow::ArrayContent
6080
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
81+
any(ImplicitFieldReadNode ifrn).shouldImplicitlyReadAllFields(node) and
82+
getElementTypeIncludingFields*(node.getType()) = containerType
83+
|
84+
c = getContentForType(containerType)
6885
)
6986
}
7087

88+
/**
89+
* Holds if default `TaintTracking::Configuration`s should allow implicit reads
90+
* of `c` at `node` in any context.
91+
*/
92+
bindingset[node]
93+
predicate defaultImplicitTaintReadGlobal(DataFlow::Node node, DataFlow::ContentSet c) {
94+
exists(Type containerType |
95+
any(ImplicitFieldReadNode ifrn).shouldImplicitlyReadAllFields(node) and
96+
getElementTypeIncludingFields*(node.getType()) = containerType
97+
|
98+
c = getContentForType(containerType)
99+
)
100+
}
101+
102+
/**
103+
* A unit class for adding nodes that should implicitly read from all nested content
104+
* in a taint-tracking context.
105+
*
106+
* For example, this might be appopriate for the argument to a method that serializes a struct.
107+
*/
108+
class ImplicitFieldReadNode extends Unit {
109+
/**
110+
* Holds if the node `n` should implicitly read from all nested content in a taint-tracking context.
111+
*/
112+
abstract predicate shouldImplicitlyReadAllFields(DataFlow::Node n);
113+
}
114+
71115
/**
72116
* A unit class for adding additional taint steps.
73117
*

go/ql/lib/semmle/go/dataflow/internal/tainttracking1/TaintTrackingImpl.qll

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ abstract deprecated class Configuration extends DataFlow::Configuration {
156156
this.isAdditionalTaintStep(node, _, _, _)
157157
) and
158158
defaultImplicitTaintRead(node, c)
159+
or
160+
defaultImplicitTaintReadGlobal(node, c)
159161
}
160162

161163
/**

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 TaintTracking::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+
}

shared/dataflow/codeql/dataflow/TaintTracking.qll

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ signature module InputSig<LocationSig Location, DF::InputSig<Location> Lang> {
2929
*/
3030
bindingset[node]
3131
predicate defaultImplicitTaintRead(Lang::Node node, Lang::ContentSet c);
32+
33+
/**
34+
* Holds if taint flow configurations should allow implicit reads of `c` in any context.
35+
*/
36+
bindingset[node]
37+
predicate defaultImplicitTaintReadGlobal(Lang::Node node, Lang::ContentSet c);
3238
}
3339

3440
/**
@@ -66,6 +72,8 @@ module TaintFlowMake<
6672
Config::isAdditionalFlowStep(node, _, _, _)
6773
) and
6874
defaultImplicitTaintRead(node, c)
75+
or
76+
defaultImplicitTaintReadGlobal(node, c)
6977
}
7078
}
7179

0 commit comments

Comments
 (0)