Skip to content

Commit f5b13db

Browse files
authored
Merge pull request #21015 from aschackmull/go/mad-barriers
Go: Support for MaD barriers and barrier guards.
2 parents c666fc7 + ca805e9 commit f5b13db

24 files changed

+261
-107
lines changed

go/ql/lib/ext/github.com.beego.beego.server.web.model.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,8 @@ extensions:
5050
- ["group:beego", "Controller", True, "GetString", "", "", "ReturnValue[0]", "remote", "manual"]
5151
- ["group:beego", "Controller", True, "GetStrings", "", "", "ReturnValue[0]", "remote", "manual"]
5252
- ["group:beego", "Controller", True, "Input", "", "", "ReturnValue[0]", "remote", "manual"]
53+
- addsTo:
54+
pack: codeql/go-all
55+
extensible: barrierModel
56+
data:
57+
- ["group:beego", "", True, "Htmlquote", "", "", "ReturnValue", "html-injection", "manual"]

go/ql/lib/ext/mime.multipart.model.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,21 @@
11
extensions:
2+
- addsTo:
3+
pack: codeql/go-all
4+
extensible: barrierModel
5+
data:
6+
# The only way to create a `mime/multipart.FileHeader` is to create a
7+
# `mime/multipart.Form`, which creates the `Filename` field of each
8+
# `mime/multipart.FileHeader` by calling `Part.FileName`, which calls
9+
# `path/filepath.Base` on its return value. In general `path/filepath.Base`
10+
# is not a sanitizer for path traversal, but in this specific case where the
11+
# output is going to be used as a filename rather than a directory name, it
12+
# is adequate.
13+
- ["mime/multipart", "FileHeader", False, "Filename", "", "", "", "path-injection", "manual"]
14+
# `Part.FileName` calls `path/filepath.Base` on its return value. In
15+
# general `path/filepath.Base` is not a sanitizer for path traversal, but in
16+
# this specific case where the output is going to be used as a filename
17+
# rather than a directory name, it is adequate.
18+
- ["mime/multipart", "Part", False, "FileName", "", "", "ReturnValue", "path-injection", "manual"]
219
- addsTo:
320
pack: codeql/go-all
421
extensible: summaryModel

go/ql/lib/ext/path.filepath.model.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
extensions:
2+
- addsTo:
3+
pack: codeql/go-all
4+
extensible: barrierModel
5+
data:
6+
- ["path/filepath", "", False, "Rel", "", "", "ReturnValue", "path-injection", "manual"]
27
- addsTo:
38
pack: codeql/go-all
49
extensible: summaryModel

go/ql/lib/semmle/go/Concepts.qll

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,10 +116,10 @@ module FileSystemAccess {
116116
}
117117
}
118118

119-
private class DefaultFileSystemAccess extends FileSystemAccess::Range, DataFlow::CallNode {
119+
private class ExternalFileSystemAccess extends FileSystemAccess::Range, DataFlow::CallNode {
120120
DataFlow::ArgumentNode pathArgument;
121121

122-
DefaultFileSystemAccess() {
122+
ExternalFileSystemAccess() {
123123
sinkNode(pathArgument, "path-injection") and
124124
this = pathArgument.getCall()
125125
}
@@ -394,10 +394,10 @@ module LoggerCall {
394394
}
395395
}
396396

397-
private class DefaultLoggerCall extends LoggerCall::Range, DataFlow::CallNode {
397+
private class ExternalLoggerCall extends LoggerCall::Range, DataFlow::CallNode {
398398
DataFlow::ArgumentNode messageComponent;
399399

400-
DefaultLoggerCall() {
400+
ExternalLoggerCall() {
401401
sinkNode(messageComponent, "log-injection") and
402402
this = messageComponent.getCall()
403403
}

go/ql/lib/semmle/go/concepts/HTTP.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -320,11 +320,11 @@ module Http {
320320
)
321321
}
322322

323-
private class DefaultHttpRedirect extends Range, DataFlow::CallNode {
323+
private class ExternalHttpRedirect extends Range, DataFlow::CallNode {
324324
DataFlow::ArgumentNode url;
325325
int rw;
326326

327-
DefaultHttpRedirect() {
327+
ExternalHttpRedirect() {
328328
this = url.getCall() and
329329
exists(string kind |
330330
sinkKindInfo(kind, rw) and

go/ql/lib/semmle/go/dataflow/ExternalFlow.qll

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,9 @@ module ModelValidation {
129129
summaryModel(_, _, _, _, _, _, path, _, _, _, _) or
130130
summaryModel(_, _, _, _, _, _, _, path, _, _, _) or
131131
sinkModel(_, _, _, _, _, _, path, _, _, _) or
132-
sourceModel(_, _, _, _, _, _, path, _, _, _)
132+
sourceModel(_, _, _, _, _, _, path, _, _, _) or
133+
barrierModel(_, _, _, _, _, _, path, _, _, _) or
134+
barrierGuardModel(_, _, _, _, _, _, path, _, _, _, _)
133135
}
134136

135137
private module MkAccessPath = AccessPathSyntax::AccessPath<getRelevantAccessPath/1>;
@@ -142,6 +144,8 @@ module ModelValidation {
142144
exists(string pred, AccessPath input, AccessPathToken part |
143145
sinkModel(_, _, _, _, _, _, input, _, _, _) and pred = "sink"
144146
or
147+
barrierGuardModel(_, _, _, _, _, _, input, _, _, _, _) and pred = "barrier guard"
148+
or
145149
summaryModel(_, _, _, _, _, _, input, _, _, _, _) and pred = "summary"
146150
|
147151
(
@@ -164,6 +168,8 @@ module ModelValidation {
164168
exists(string pred, AccessPath output, AccessPathToken part |
165169
sourceModel(_, _, _, _, _, _, output, _, _, _) and pred = "source"
166170
or
171+
barrierModel(_, _, _, _, _, _, output, _, _, _) and pred = "barrier"
172+
or
167173
summaryModel(_, _, _, _, _, _, _, output, _, _, _) and pred = "summary"
168174
|
169175
(
@@ -181,7 +187,13 @@ module ModelValidation {
181187
private module KindValConfig implements SharedModelVal::KindValidationConfigSig {
182188
predicate summaryKind(string kind) { summaryModel(_, _, _, _, _, _, _, _, kind, _, _) }
183189

184-
predicate sinkKind(string kind) { sinkModel(_, _, _, _, _, _, _, kind, _, _) }
190+
predicate sinkKind(string kind) {
191+
sinkModel(_, _, _, _, _, _, _, kind, _, _)
192+
or
193+
barrierModel(_, _, _, _, _, _, _, kind, _, _)
194+
or
195+
barrierGuardModel(_, _, _, _, _, _, _, _, kind, _, _)
196+
}
185197

186198
predicate sourceKind(string kind) { sourceModel(_, _, _, _, _, _, _, kind, _, _) }
187199

@@ -199,6 +211,11 @@ module ModelValidation {
199211
or
200212
sinkModel(package, type, _, name, signature, ext, _, _, provenance, _) and pred = "sink"
201213
or
214+
barrierModel(package, type, _, name, signature, ext, _, _, provenance, _) and pred = "barrier"
215+
or
216+
barrierGuardModel(package, type, _, name, signature, ext, _, _, _, provenance, _) and
217+
pred = "barrier guard"
218+
or
202219
summaryModel(package, type, _, name, signature, ext, _, _, _, provenance, _) and
203220
pred = "summary"
204221
or
@@ -224,6 +241,14 @@ module ModelValidation {
224241
invalidProvenance(provenance) and
225242
result = "Unrecognized provenance description \"" + provenance + "\" in " + pred + " model."
226243
)
244+
or
245+
exists(string acceptingvalue |
246+
barrierGuardModel(_, _, _, _, _, _, _, acceptingvalue, _, _, _) and
247+
invalidAcceptingValue(acceptingvalue) and
248+
result =
249+
"Unrecognized accepting value description \"" + acceptingvalue +
250+
"\" in barrier guard model."
251+
)
227252
}
228253

229254
private string getInvalidPackageGroup() {
@@ -232,6 +257,11 @@ module ModelValidation {
232257
or
233258
FlowExtensions::sinkModel(package, _, _, _, _, _, _, _, _, _) and pred = "sink"
234259
or
260+
FlowExtensions::barrierModel(package, _, _, _, _, _, _, _, _, _) and pred = "barrier"
261+
or
262+
FlowExtensions::barrierGuardModel(package, _, _, _, _, _, _, _, _, _, _) and
263+
pred = "barrier guard"
264+
or
235265
FlowExtensions::summaryModel(package, _, _, _, _, _, _, _, _, _, _) and
236266
pred = "summary"
237267
or
@@ -262,6 +292,10 @@ private predicate elementSpec(
262292
or
263293
sinkModel(package, type, subtypes, name, signature, ext, _, _, _, _)
264294
or
295+
barrierModel(package, type, subtypes, name, signature, ext, _, _, _, _)
296+
or
297+
barrierGuardModel(package, type, subtypes, name, signature, ext, _, _, _, _, _)
298+
or
265299
summaryModel(package, type, subtypes, name, signature, ext, _, _, _, _, _)
266300
or
267301
neutralModel(package, type, name, signature, _, _) and ext = "" and subtypes = false
@@ -397,6 +431,54 @@ private module Cached {
397431
isSinkNode(n, kind, model) and n.asNode() = node
398432
)
399433
}
434+
435+
private newtype TKindModelPair =
436+
TMkPair(string kind, string model) { isBarrierGuardNode(_, _, kind, model) }
437+
438+
private boolean convertAcceptingValue(Public::AcceptingValue av) {
439+
av.isTrue() and result = true
440+
or
441+
av.isFalse() and result = false
442+
// Remaining cases are not supported yet, they depend on the shared Guards library.
443+
// or
444+
// av.isNoException() and result.getDualValue().isThrowsException()
445+
// or
446+
// av.isZero() and result.asIntValue() = 0
447+
// or
448+
// av.isNotZero() and result.getDualValue().asIntValue() = 0
449+
// or
450+
// av.isNull() and result.isNullValue()
451+
// or
452+
// av.isNotNull() and result.isNonNullValue()
453+
}
454+
455+
private predicate barrierGuardChecks(DataFlow::Node g, Expr e, boolean gv, TKindModelPair kmp) {
456+
exists(
457+
SourceSinkInterpretationInput::InterpretNode n, Public::AcceptingValue acceptingvalue,
458+
string kind, string model
459+
|
460+
isBarrierGuardNode(n, acceptingvalue, kind, model) and
461+
n.asNode().asExpr() = e and
462+
kmp = TMkPair(kind, model) and
463+
gv = convertAcceptingValue(acceptingvalue)
464+
|
465+
g.asExpr().(CallExpr).getAnArgument() = e // TODO: qualifier?
466+
)
467+
}
468+
469+
/**
470+
* Holds if `node` is specified as a barrier with the given kind in a MaD flow
471+
* model.
472+
*/
473+
cached
474+
predicate barrierNode(DataFlow::Node node, string kind, string model) {
475+
exists(SourceSinkInterpretationInput::InterpretNode n |
476+
isBarrierNode(n, kind, model) and n.asNode() = node
477+
)
478+
or
479+
DataFlow::ParameterizedBarrierGuard<TKindModelPair, barrierGuardChecks/4>::getABarrierNode(TMkPair(kind,
480+
model)) = node
481+
}
400482
}
401483

402484
import Cached
@@ -413,6 +495,12 @@ predicate sourceNode(DataFlow::Node node, string kind) { sourceNode(node, kind,
413495
*/
414496
predicate sinkNode(DataFlow::Node node, string kind) { sinkNode(node, kind, _) }
415497

498+
/**
499+
* Holds if `node` is specified as a barrier with the given kind in a MaD flow
500+
* model.
501+
*/
502+
predicate barrierNode(DataFlow::Node node, string kind) { barrierNode(node, kind, _) }
503+
416504
// adapter class for converting Mad summaries to `SummarizedCallable`s
417505
private class SummarizedCallableAdapter extends Public::SummarizedCallable {
418506
SummarizedCallableAdapter() { summaryElement(this, _, _, _, _, _) }

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

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -339,29 +339,67 @@ class ContentSet instanceof TContentSet {
339339
*/
340340
signature predicate guardChecksSig(Node g, Expr e, boolean branch);
341341

342+
bindingset[this]
343+
private signature class ParamSig;
344+
345+
private module WithParam<ParamSig P> {
346+
/**
347+
* Holds if the guard `g` validates the expression `e` upon evaluating to `branch`.
348+
*
349+
* The expression `e` is expected to be a syntactic part of the guard `g`.
350+
* For example, the guard `g` might be a call `isSafe(x)` and the expression `e`
351+
* the argument `x`.
352+
*/
353+
signature predicate guardChecksSig(Node g, Expr e, boolean branch, P param);
354+
}
355+
342356
/**
343357
* Provides a set of barrier nodes for a guard that validates an expression.
344358
*
345359
* This is expected to be used in `isBarrier`/`isSanitizer` definitions
346360
* in data flow and taint tracking.
347361
*/
348362
module BarrierGuard<guardChecksSig/3 guardChecks> {
363+
private predicate guardChecks(Node g, Expr e, boolean branch, Unit param) {
364+
guardChecks(g, e, branch) and exists(param)
365+
}
366+
367+
private module B = ParameterizedBarrierGuard<Unit, guardChecks/4>;
368+
369+
/** Gets a node that is safely guarded by the given guard check. */
370+
Node getABarrierNode() { result = B::getABarrierNode(_) }
371+
372+
/**
373+
* Gets a node that is safely guarded by the given guard check.
374+
*/
375+
Node getABarrierNodeForGuard(Node guardCheck) {
376+
result = B::getABarrierNodeForGuard(guardCheck, _)
377+
}
378+
}
379+
380+
/**
381+
* Provides a set of barrier nodes for a guard that validates an expression.
382+
*
383+
* This is expected to be used in `isBarrier`/`isSanitizer` definitions
384+
* in data flow and taint tracking.
385+
*/
386+
module ParameterizedBarrierGuard<ParamSig P, WithParam<P>::guardChecksSig/4 guardChecks> {
349387
/** Gets a node that is safely guarded by the given guard check. */
350-
Node getABarrierNode() {
388+
Node getABarrierNode(P param) {
351389
exists(ControlFlow::ConditionGuardNode guard, SsaWithFields var |
352390
result = pragma[only_bind_out](var).getAUse()
353391
|
354-
guards(_, guard, _, var) and
392+
guards(_, guard, _, var, param) and
355393
pragma[only_bind_out](guard).dominates(result.getBasicBlock())
356394
)
357395
}
358396

359397
/**
360398
* Gets a node that is safely guarded by the given guard check.
361399
*/
362-
Node getABarrierNodeForGuard(Node guardCheck) {
400+
Node getABarrierNodeForGuard(Node guardCheck, P param) {
363401
exists(ControlFlow::ConditionGuardNode guard, SsaWithFields var | result = var.getAUse() |
364-
guards(guardCheck, guard, _, var) and
402+
guards(guardCheck, guard, _, var, param) and
365403
guard.dominates(result.getBasicBlock())
366404
)
367405
}
@@ -373,22 +411,24 @@ module BarrierGuard<guardChecksSig/3 guardChecks> {
373411
* This predicate exists to enforce a good join order in `getAGuardedNode`.
374412
*/
375413
pragma[noinline]
376-
private predicate guards(Node g, ControlFlow::ConditionGuardNode guard, Node nd, SsaWithFields ap) {
377-
guards(g, guard, nd) and nd = ap.getAUse()
414+
private predicate guards(
415+
Node g, ControlFlow::ConditionGuardNode guard, Node nd, SsaWithFields ap, P param
416+
) {
417+
guards(g, guard, nd, param) and nd = ap.getAUse()
378418
}
379419

380420
/**
381421
* Holds if `guard` marks a point in the control-flow graph where `g`
382422
* is known to validate `nd`.
383423
*/
384-
private predicate guards(Node g, ControlFlow::ConditionGuardNode guard, Node nd) {
424+
private predicate guards(Node g, ControlFlow::ConditionGuardNode guard, Node nd, P param) {
385425
exists(boolean branch |
386-
guardChecks(g, nd.asExpr(), branch) and
426+
guardChecks(g, nd.asExpr(), branch, param) and
387427
guard.ensures(g, branch)
388428
)
389429
or
390430
exists(DataFlow::Property p, Node resNode, Node check, boolean outcome |
391-
guardingCall(g, _, _, _, p, _, nd, resNode) and
431+
guardingCall(g, _, _, _, p, _, nd, resNode, param) and
392432
p.checkOn(check, outcome, resNode) and
393433
guard.ensures(pragma[only_bind_into](check), outcome)
394434
)
@@ -405,9 +445,9 @@ module BarrierGuard<guardChecksSig/3 guardChecks> {
405445
pragma[noinline]
406446
private predicate guardingCall(
407447
Node g, Function f, FunctionInput inp, FunctionOutput outp, DataFlow::Property p, CallNode c,
408-
Node nd, Node resNode
448+
Node nd, Node resNode, P param
409449
) {
410-
guardingFunction(g, f, inp, outp, p) and
450+
guardingFunction(g, f, inp, outp, p, param) and
411451
c = f.getACall() and
412452
nd = getInputNode(inp, c) and
413453
localFlow(getOutputNode(outp, c), resNode)
@@ -438,15 +478,15 @@ module BarrierGuard<guardChecksSig/3 guardChecks> {
438478
* `false`, `nil` or a non-`nil` value.)
439479
*/
440480
private predicate guardingFunction(
441-
Node g, Function f, FunctionInput inp, FunctionOutput outp, DataFlow::Property p
481+
Node g, Function f, FunctionInput inp, FunctionOutput outp, DataFlow::Property p, P param
442482
) {
443483
exists(FuncDecl fd, Node arg, Node ret |
444484
fd.getFunction() = f and
445485
localFlow(inp.getExitNode(fd), pragma[only_bind_out](arg)) and
446486
(
447487
// Case: a function like "if someBarrierGuard(arg) { return true } else { return false }"
448488
exists(ControlFlow::ConditionGuardNode guard |
449-
guards(g, pragma[only_bind_out](guard), arg) and
489+
guards(g, pragma[only_bind_out](guard), arg, param) and
450490
guard.dominates(pragma[only_bind_out](ret).getBasicBlock())
451491
|
452492
onlyPossibleReturnSatisfyingProperty(fd, outp, ret, p)
@@ -456,7 +496,7 @@ module BarrierGuard<guardChecksSig/3 guardChecks> {
456496
// or "return !someBarrierGuard(arg) && otherCond(...)"
457497
exists(boolean outcome |
458498
ret = getUniqueOutputNode(fd, outp) and
459-
guardChecks(g, arg.asExpr(), outcome) and
499+
guardChecks(g, arg.asExpr(), outcome, param) and
460500
// This predicate's contract is (p holds of ret ==> arg is checked),
461501
// (and we have (this has outcome ==> arg is checked))
462502
// but p.checkOn(ret, outcome, this) gives us (ret has outcome ==> p holds of this),
@@ -471,7 +511,7 @@ module BarrierGuard<guardChecksSig/3 guardChecks> {
471511
DataFlow::Property outpProp
472512
|
473513
ret = getUniqueOutputNode(fd, outp) and
474-
guardingFunction(g, f2, inp2, outp2, outpProp) and
514+
guardingFunction(g, f2, inp2, outp2, outpProp, param) and
475515
c = f2.getACall() and
476516
arg = inp2.getNode(c) and
477517
(

0 commit comments

Comments
 (0)