Skip to content
This repository was archived by the owner on Jan 5, 2023. It is now read-only.

Commit 228e95a

Browse files
Max SchaeferGitHub Enterprise
Max Schaefer
authored and
GitHub Enterprise
committed
Merge pull request #185 from sauyon/open-redirect-fp1
OpenRedirect: treat assignments to Url.Path as a barrier
2 parents d8c6361 + 81ba71e commit 228e95a

File tree

5 files changed

+101
-32
lines changed

5 files changed

+101
-32
lines changed

ql/src/semmle/go/controlflow/ControlFlowGraph.qll

+12
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,18 @@ module ControlFlow {
5757
/** Gets the basic block to which this node belongs. */
5858
BasicBlock getBasicBlock() { result.getANode() = this }
5959

60+
/** Holds if this node dominates `dominee` in the control-flow graph. */
61+
pragma[inline]
62+
predicate dominatesNode(ControlFlow::Node dominee) {
63+
exists(ReachableBasicBlock thisbb, ReachableBasicBlock dbb, int i, int j |
64+
this = thisbb.getNode(i) and dominee = dbb.getNode(j)
65+
|
66+
thisbb.strictlyDominates(dbb)
67+
or
68+
thisbb = dbb and i <= j
69+
)
70+
}
71+
6072
/** Gets the innermost function or file to which this node belongs. */
6173
Root getRoot() { none() }
6274

ql/src/semmle/go/dataflow/SSA.qll

+64
Original file line numberDiff line numberDiff line change
@@ -288,3 +288,67 @@ class SsaPhiNode extends SsaPseudoDefinition, TPhi {
288288
getBasicBlock().hasLocationInfo(filepath, startline, startcolumn, _, _)
289289
}
290290
}
291+
292+
/**
293+
* An SSA variable, possibly with a chain of field reads on it.
294+
*/
295+
private newtype TSsaWithFields =
296+
TRoot(SsaVariable v) or
297+
TStep(SsaWithFields base, Field f) { exists(accessPathAux(base, f)) }
298+
299+
/**
300+
* Gets a representation of `nd` as an ssa-with-fields value if there is one.
301+
*/
302+
private TSsaWithFields accessPath(IR::Instruction insn) {
303+
exists(SsaVariable v | insn = v.getAUse() | result = TRoot(v))
304+
or
305+
exists(SsaWithFields base, Field f | insn = accessPathAux(base, f) | result = TStep(base, f))
306+
}
307+
308+
/**
309+
* Gets a data-flow node that reads a field `f` from a node that is represented
310+
* by ssa-with-fields value `base`.
311+
*/
312+
private IR::Instruction accessPathAux(TSsaWithFields base, Field f) {
313+
exists(IR::FieldReadInstruction fr | fr = result |
314+
base = accessPath(fr.getBase()) and
315+
f = fr.getField()
316+
)
317+
}
318+
319+
class SsaWithFields extends TSsaWithFields {
320+
/**
321+
* Gets the SSA variable corresponding to the base of this SSA variable with fields.
322+
*
323+
* For example, the SSA variable corresponding to `a` for the SSA variable with fields
324+
* corresponding to `a.b`.
325+
*/
326+
SsaVariable getBaseVariable() {
327+
this = TRoot(result)
328+
or
329+
exists(SsaWithFields base, Field f | this = TStep(base, f) | result = base.getBaseVariable())
330+
}
331+
332+
/** Gets a use that refers to this SSA variable with fields. */
333+
DataFlow::Node getAUse() { this = accessPath(result.asInstruction()) }
334+
335+
/** Gets a textual representation of this element. */
336+
string toString() {
337+
exists(SsaVariable var | this = TRoot(var) | result = "(" + var + ")")
338+
or
339+
exists(SsaWithFields base, Field f | this = TStep(base, f) | result = base + "." + f.getName())
340+
}
341+
342+
/**
343+
* Holds if this element is at the specified location.
344+
* The location spans column `startcolumn` of line `startline` to
345+
* column `endcolumn` of line `endline` in file `filepath`.
346+
* For more information, see
347+
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
348+
*/
349+
predicate hasLocationInfo(
350+
string filepath, int startline, int startcolumn, int endline, int endcolumn
351+
) {
352+
this.getBaseVariable().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
353+
}
354+
}

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

+5-31
Original file line numberDiff line numberDiff line change
@@ -733,33 +733,6 @@ predicate localFlowStep(Node nodeFrom, Node nodeTo) {
733733
*/
734734
predicate localFlow(Node source, Node sink) { localFlowStep*(source, sink) }
735735

736-
/**
737-
* An SSA variable, possibly with a chain of field reads on it.
738-
*/
739-
private newtype SsaWithFields =
740-
Root(SsaVariable v) or
741-
Step(SsaWithFields base, Field f) { exists(accessPathAux(base, f)) }
742-
743-
/**
744-
* Gets a representation of `nd` as an ssa-with-fields value if there is one.
745-
*/
746-
private SsaWithFields accessPath(Node nd) {
747-
exists(SsaVariable v | nd.asInstruction() = v.getAUse() | result = Root(v))
748-
or
749-
exists(SsaWithFields base, Field f | nd = accessPathAux(base, f) | result = Step(base, f))
750-
}
751-
752-
/**
753-
* Gets a data-flow node that reads a field `f` from a node that is represented
754-
* by ssa-with-fields value `base`.
755-
*/
756-
private Node accessPathAux(SsaWithFields base, Field f) {
757-
exists(IR::FieldReadInstruction fr | fr = result.asInstruction() |
758-
base = accessPath(instructionNode(fr.getBase())) and
759-
f = fr.getField()
760-
)
761-
}
762-
763736
/**
764737
* A guard that validates some expression.
765738
*
@@ -775,8 +748,10 @@ abstract class BarrierGuard extends Node {
775748

776749
/** Gets a node guarded by this guard. */
777750
final Node getAGuardedNode() {
778-
exists(ControlFlow::ConditionGuardNode guard, Node nd |
779-
guards(guard, nd, accessPath(result)) and
751+
exists(ControlFlow::ConditionGuardNode guard, Node nd, SsaWithFields var |
752+
result = var.getAUse()
753+
|
754+
guards(guard, nd, var) and
780755
guard.dominates(result.asInstruction().getBasicBlock())
781756
)
782757
}
@@ -789,8 +764,7 @@ abstract class BarrierGuard extends Node {
789764
*/
790765
pragma[noinline]
791766
private predicate guards(ControlFlow::ConditionGuardNode guard, Node nd, SsaWithFields ap) {
792-
guards(guard, nd) and
793-
ap = accessPath(nd)
767+
guards(guard, nd) and nd = ap.getAUse()
794768
}
795769

796770
/**

ql/src/semmle/go/security/OpenUrlRedirectCustomizations.qll

+19
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,25 @@ module OpenUrlRedirect {
5050
}
5151
}
5252

53+
/**
54+
* An access to a variable that is preceded by an assignment to its `Path` field.
55+
*
56+
* This is overapproximate; this will currently remove flow through all `Url.Path` assignments
57+
* which contain a substring that could sanitize data.
58+
*/
59+
class PathAssignmentBarrier extends Barrier, Read {
60+
PathAssignmentBarrier() {
61+
exists(Write w, Field f, SsaWithFields var |
62+
f.getName() = "Path" and
63+
hasHostnameSanitizingSubstring(w.getRhs()) and
64+
this = var.getAUse()
65+
|
66+
w.writesField(var.getAUse(), f, _) and
67+
w.dominatesNode(insn)
68+
)
69+
}
70+
}
71+
5372
/**
5473
* A call to a function called `isLocalUrl` or similar, which is
5574
* considered a barrier for purposes of URL redirection.

ql/src/semmle/go/security/UrlConcatenation.qll

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ predicate sanitizingPrefixEdge(DataFlow::Node source, DataFlow::Node sink) {
5555
* In the latter two cases, the additional check is necessary to avoid a `/` that could be interpreted as
5656
* the `//` separating the (optional) scheme from the hostname.
5757
*/
58-
private predicate hasHostnameSanitizingSubstring(DataFlow::Node nd) {
58+
predicate hasHostnameSanitizingSubstring(DataFlow::Node nd) {
5959
nd.getStringValue().regexpMatch(".*([?#]|[^?#:/\\\\][/\\\\]).*|[/\\\\][^/\\\\].*")
6060
or
6161
hasHostnameSanitizingSubstring(StringConcatenation::getAnOperand(nd))

0 commit comments

Comments
 (0)