Skip to content

Commit ce4be6d

Browse files
committed
Refactor to use flow state instead of 3 flow configs (copilot)
1 parent ca85f0b commit ce4be6d

File tree

1 file changed

+53
-103
lines changed

1 file changed

+53
-103
lines changed

go/ql/src/Security/CWE-079/HTMLTemplateEscapingPassthrough.ql

Lines changed: 53 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,6 @@
1313

1414
import go
1515

16-
/**
17-
* Holds if the provided `untrusted` node flows into a conversion to a PassthroughType.
18-
* The `targetType` parameter gets populated with the name of the PassthroughType,
19-
* and `conversionSink` gets populated with the node where the conversion happens.
20-
*/
21-
predicate flowsFromUntrustedToConversion(
22-
DataFlow::Node untrusted, PassthroughTypeName targetType, DataFlow::Node conversionSink
23-
) {
24-
exists(DataFlow::Node source |
25-
UntrustedToPassthroughTypeConversionFlow::flow(source, conversionSink) and
26-
source = untrusted and
27-
UntrustedToPassthroughTypeConversionConfig::isSinkToPassthroughType(conversionSink, targetType)
28-
)
29-
}
30-
3116
/**
3217
* A name of a type that will not be escaped when passed to
3318
* a `html/template` template.
@@ -36,65 +21,6 @@ class PassthroughTypeName extends string {
3621
PassthroughTypeName() { this = ["HTML", "HTMLAttr", "JS", "JSStr", "CSS", "Srcset", "URL"] }
3722
}
3823

39-
module UntrustedToPassthroughTypeConversionConfig implements DataFlow::ConfigSig {
40-
predicate isSource(DataFlow::Node source) { source instanceof ActiveThreatModelSource }
41-
42-
additional predicate isSinkToPassthroughType(DataFlow::TypeCastNode sink, PassthroughTypeName name) {
43-
exists(Type typ |
44-
typ = sink.getResultType() and
45-
typ.getUnderlyingType*().hasQualifiedName("html/template", name)
46-
)
47-
}
48-
49-
predicate isSink(DataFlow::Node sink) { isSinkToPassthroughType(sink, _) }
50-
51-
predicate isBarrier(DataFlow::Node node) {
52-
node instanceof SharedXss::Sanitizer or node.getType() instanceof NumericType
53-
}
54-
}
55-
56-
/**
57-
* Tracks taint flow for reasoning about when a `ActiveThreatModelSource` is
58-
* converted into a special "passthrough" type which will not be escaped by the
59-
* template generator; this allows the injection of arbitrary content (html,
60-
* css, js) into the generated output of the templates.
61-
*/
62-
module UntrustedToPassthroughTypeConversionFlow =
63-
TaintTracking::Global<UntrustedToPassthroughTypeConversionConfig>;
64-
65-
/**
66-
* Holds if the provided `conversion` node flows into the provided `execSink`.
67-
*/
68-
predicate flowsFromConversionToExec(
69-
DataFlow::Node conversion, PassthroughTypeName targetType, DataFlow::Node execSink
70-
) {
71-
PassthroughTypeConversionToTemplateExecutionCallFlow::flow(conversion, execSink) and
72-
PassthroughTypeConversionToTemplateExecutionCallConfig::isSourceConversionToPassthroughType(conversion,
73-
targetType)
74-
}
75-
76-
module PassthroughTypeConversionToTemplateExecutionCallConfig implements DataFlow::ConfigSig {
77-
predicate isSource(DataFlow::Node source) { isSourceConversionToPassthroughType(source, _) }
78-
79-
additional predicate isSourceConversionToPassthroughType(
80-
DataFlow::TypeCastNode source, PassthroughTypeName name
81-
) {
82-
exists(Type typ |
83-
typ = source.getResultType() and
84-
typ.getUnderlyingType*().hasQualifiedName("html/template", name)
85-
)
86-
}
87-
88-
predicate isSink(DataFlow::Node sink) { isSinkToTemplateExec(sink, _) }
89-
}
90-
91-
/**
92-
* Tracks taint flow for reasoning about when the result of a conversion to a
93-
* PassthroughType flows to a template execution call.
94-
*/
95-
module PassthroughTypeConversionToTemplateExecutionCallFlow =
96-
TaintTracking::Global<PassthroughTypeConversionToTemplateExecutionCallConfig>;
97-
9824
/**
9925
* Holds if the sink is a data value argument of a template execution call.
10026
*/
@@ -109,46 +35,70 @@ predicate isSinkToTemplateExec(DataFlow::Node sink, DataFlow::CallNode call) {
10935
)
11036
}
11137

112-
module FromUntrustedToTemplateExecutionCallConfig implements DataFlow::ConfigSig {
113-
predicate isSource(DataFlow::Node source) { source instanceof ActiveThreatModelSource }
38+
/**
39+
* Flow state for tracking whether a conversion to a passthrough type has occurred.
40+
*/
41+
class FlowState extends int {
42+
FlowState() { this = 0 or this = 1 }
43+
44+
predicate isBeforeConversion() { this = 0 }
11445

115-
predicate isSink(DataFlow::Node sink) { isSinkToTemplateExec(sink, _) }
46+
predicate isAfterConversion() { this = 1 }
11647
}
11748

11849
/**
119-
* Tracks taint flow from a `ActiveThreatModelSource` into a template executor
120-
* call.
50+
* Data flow configuration that tracks flows from untrusted sources (A) to template execution calls (C),
51+
* and tracks whether a conversion to a passthrough type (B) has occurred.
12152
*/
122-
module FromUntrustedToTemplateExecutionCallFlow =
123-
TaintTracking::Global<FromUntrustedToTemplateExecutionCallConfig>;
53+
module UntrustedToTemplateExecWithConversionConfig implements DataFlow::ConfigSig {
54+
predicate isSource(DataFlow::Node source, FlowState state) {
55+
state.isBeforeConversion() and source instanceof ActiveThreatModelSource
56+
}
57+
58+
predicate isSink(DataFlow::Node sink, FlowState state) {
59+
state.isAfterConversion() and isSinkToTemplateExec(sink, _)
60+
}
12461

125-
import FromUntrustedToTemplateExecutionCallFlow::PathGraph
62+
predicate isBarrier(DataFlow::Node node, FlowState state) {
63+
node instanceof SharedXss::Sanitizer or node.getType() instanceof NumericType
64+
}
12665

127-
/**
128-
* Holds if the provided `untrusted` node flows into the provided `execSink`.
129-
*/
130-
predicate flowsFromUntrustedToExec(
131-
FromUntrustedToTemplateExecutionCallFlow::PathNode untrusted,
132-
FromUntrustedToTemplateExecutionCallFlow::PathNode execSink
133-
) {
134-
FromUntrustedToTemplateExecutionCallFlow::flowPath(untrusted, execSink)
66+
/**
67+
* When a conversion to a passthrough type is encountered, transition the flow state.
68+
*/
69+
predicate step(DataFlow::Node pred, FlowState predState, DataFlow::Node succ, FlowState succState) {
70+
// If not yet converted, look for a conversion to a passthrough type
71+
predState.isBeforeConversion() and
72+
succState.isAfterConversion() and
73+
pred = succ and
74+
exists(Type typ, PassthroughTypeName name |
75+
typ = pred.getType() and
76+
typ.getUnderlyingType*().hasQualifiedName("html/template", name)
77+
)
78+
or
79+
// Otherwise, normal flow with unchanged state
80+
predState = succState
81+
}
13582
}
13683

84+
module UntrustedToTemplateExecWithConversionFlow =
85+
DataFlow::PathGraph<UntrustedToTemplateExecWithConversionConfig>;
86+
13787
from
138-
FromUntrustedToTemplateExecutionCallFlow::PathNode untrustedSource,
139-
FromUntrustedToTemplateExecutionCallFlow::PathNode templateExecCall,
140-
PassthroughTypeName targetTypeName, DataFlow::Node conversion
88+
UntrustedToTemplateExecWithConversionFlow::PathNode untrustedSource,
89+
UntrustedToTemplateExecWithConversionFlow::PathNode templateExecCall, DataFlow::Node conversion,
90+
PassthroughTypeName targetTypeName
14191
where
142-
// A = untrusted remote flow source
143-
// B = conversion to PassthroughType
144-
// C = template execution call
145-
// Flows:
146-
// A -> B
147-
flowsFromUntrustedToConversion(untrustedSource.getNode(), targetTypeName, conversion) and
148-
// B -> C
149-
flowsFromConversionToExec(conversion, targetTypeName, templateExecCall.getNode()) and
150-
// A -> C
151-
flowsFromUntrustedToExec(untrustedSource, templateExecCall)
92+
UntrustedToTemplateExecWithConversionFlow::flowPath(untrustedSource, templateExecCall) and
93+
// Find the conversion node in the path
94+
exists(int i |
95+
i = templateExecCall.getPathIndex() - 1 and
96+
conversion = templateExecCall.getPathNode(i).getNode() and
97+
exists(Type typ |
98+
typ = conversion.getType() and
99+
typ.getUnderlyingType*().hasQualifiedName("html/template", targetTypeName)
100+
)
101+
)
152102
select templateExecCall.getNode(), untrustedSource, templateExecCall,
153103
"Data from an $@ will not be auto-escaped because it was $@ to template." + targetTypeName,
154104
untrustedSource.getNode(), "untrusted source", conversion, "converted"

0 commit comments

Comments
 (0)