Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions go/ql/lib/ext/github.com.beego.beego.server.web.model.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,8 @@ extensions:
- ["group:beego", "Controller", True, "GetString", "", "", "ReturnValue[0]", "remote", "manual"]
- ["group:beego", "Controller", True, "GetStrings", "", "", "ReturnValue[0]", "remote", "manual"]
- ["group:beego", "Controller", True, "Input", "", "", "ReturnValue[0]", "remote", "manual"]
- addsTo:
pack: codeql/go-all
extensible: barrierModel
data:
- ["group:beego", "", True, "Htmlquote", "", "", "ReturnValue", "html-injection", "manual"]
17 changes: 17 additions & 0 deletions go/ql/lib/ext/mime.multipart.model.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,21 @@
extensions:
- addsTo:
pack: codeql/go-all
extensible: barrierModel
data:
# The only way to create a `mime/multipart.FileHeader` is to create a
# `mime/multipart.Form`, which creates the `Filename` field of each
# `mime/multipart.FileHeader` by calling `Part.FileName`, which calls
# `path/filepath.Base` on its return value. In general `path/filepath.Base`
# is not a sanitizer for path traversal, but in this specific case where the
# output is going to be used as a filename rather than a directory name, it
# is adequate.
- ["mime/multipart", "FileHeader", False, "Filename", "", "", "", "path-injection", "manual"]
# `Part.FileName` calls `path/filepath.Base` on its return value. In
# general `path/filepath.Base` is not a sanitizer for path traversal, but in
# this specific case where the output is going to be used as a filename
# rather than a directory name, it is adequate.
- ["mime/multipart", "Part", False, "FileName", "", "", "ReturnValue", "path-injection", "manual"]
- addsTo:
pack: codeql/go-all
extensible: summaryModel
Expand Down
5 changes: 5 additions & 0 deletions go/ql/lib/ext/path.filepath.model.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
extensions:
- addsTo:
pack: codeql/go-all
extensible: barrierModel
data:
- ["path/filepath", "", False, "Rel", "", "", "ReturnValue", "path-injection", "manual"]
- addsTo:
pack: codeql/go-all
extensible: summaryModel
Expand Down
8 changes: 4 additions & 4 deletions go/ql/lib/semmle/go/Concepts.qll
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,10 @@ module FileSystemAccess {
}
}

private class DefaultFileSystemAccess extends FileSystemAccess::Range, DataFlow::CallNode {
private class ExternalFileSystemAccess extends FileSystemAccess::Range, DataFlow::CallNode {
DataFlow::ArgumentNode pathArgument;

DefaultFileSystemAccess() {
ExternalFileSystemAccess() {
sinkNode(pathArgument, "path-injection") and
this = pathArgument.getCall()
}
Expand Down Expand Up @@ -394,10 +394,10 @@ module LoggerCall {
}
}

private class DefaultLoggerCall extends LoggerCall::Range, DataFlow::CallNode {
private class ExternalLoggerCall extends LoggerCall::Range, DataFlow::CallNode {
DataFlow::ArgumentNode messageComponent;

DefaultLoggerCall() {
ExternalLoggerCall() {
sinkNode(messageComponent, "log-injection") and
this = messageComponent.getCall()
}
Expand Down
4 changes: 2 additions & 2 deletions go/ql/lib/semmle/go/concepts/HTTP.qll
Original file line number Diff line number Diff line change
Expand Up @@ -320,11 +320,11 @@ module Http {
)
}

private class DefaultHttpRedirect extends Range, DataFlow::CallNode {
private class ExternalHttpRedirect extends Range, DataFlow::CallNode {
DataFlow::ArgumentNode url;
int rw;

DefaultHttpRedirect() {
ExternalHttpRedirect() {
this = url.getCall() and
exists(string kind |
sinkKindInfo(kind, rw) and
Expand Down
58 changes: 58 additions & 0 deletions go/ql/lib/semmle/go/dataflow/ExternalFlow.qll
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,10 @@ private predicate elementSpec(
or
sinkModel(package, type, subtypes, name, signature, ext, _, _, _, _)
or
barrierModel(package, type, subtypes, name, signature, ext, _, _, _, _)
or
barrierGuardModel(package, type, subtypes, name, signature, ext, _, _, _, _, _)
or
summaryModel(package, type, subtypes, name, signature, ext, _, _, _, _, _)
or
neutralModel(package, type, name, signature, _, _) and ext = "" and subtypes = false
Expand Down Expand Up @@ -397,6 +401,54 @@ private module Cached {
isSinkNode(n, kind, model) and n.asNode() = node
)
}

private newtype TKindModelPair =
TMkPair(string kind, string model) { isBarrierGuardNode(_, _, kind, model) }

private boolean convertAcceptingValue(Public::AcceptingValue av) {
av.isTrue() and result = true
or
av.isFalse() and result = false
// Remaining cases are not supported yet, they depend on the shared Guards library.
// or
// av.isNoException() and result.getDualValue().isThrowsException()
// or
// av.isZero() and result.asIntValue() = 0
// or
// av.isNotZero() and result.getDualValue().asIntValue() = 0
// or
// av.isNull() and result.isNullValue()
// or
// av.isNotNull() and result.isNonNullValue()
}

private predicate barrierGuardChecks(DataFlow::Node g, Expr e, boolean gv, TKindModelPair kmp) {
exists(
SourceSinkInterpretationInput::InterpretNode n, Public::AcceptingValue acceptingvalue,
string kind, string model
|
isBarrierGuardNode(n, acceptingvalue, kind, model) and
n.asNode().asExpr() = e and
kmp = TMkPair(kind, model) and
gv = convertAcceptingValue(acceptingvalue)
|
g.asExpr().(CallExpr).getAnArgument() = e // TODO: qualifier?
)
}

/**
* Holds if `node` is specified as a barrier with the given kind in a MaD flow
* model.
*/
cached
predicate barrierNode(DataFlow::Node node, string kind, string model) {
exists(SourceSinkInterpretationInput::InterpretNode n |
isBarrierNode(n, kind, model) and n.asNode() = node
)
or
DataFlow::ParameterizedBarrierGuard<TKindModelPair, barrierGuardChecks/4>::getABarrierNode(TMkPair(kind,
model)) = node
}
}

import Cached
Expand All @@ -413,6 +465,12 @@ predicate sourceNode(DataFlow::Node node, string kind) { sourceNode(node, kind,
*/
predicate sinkNode(DataFlow::Node node, string kind) { sinkNode(node, kind, _) }

/**
* Holds if `node` is specified as a barrier with the given kind in a MaD flow
* model.
*/
predicate barrierNode(DataFlow::Node node, string kind) { barrierNode(node, kind, _) }

// adapter class for converting Mad summaries to `SummarizedCallable`s
private class SummarizedCallableAdapter extends Public::SummarizedCallable {
SummarizedCallableAdapter() { summaryElement(this, _, _, _, _, _) }
Expand Down
70 changes: 55 additions & 15 deletions go/ql/lib/semmle/go/dataflow/internal/DataFlowUtil.qll
Original file line number Diff line number Diff line change
Expand Up @@ -339,29 +339,67 @@
*/
signature predicate guardChecksSig(Node g, Expr e, boolean branch);

bindingset[this]
private signature class ParamSig;

private module WithParam<ParamSig P> {
/**
* Holds if the guard `g` validates the expression `e` upon evaluating to `branch`.
*
* The expression `e` is expected to be a syntactic part of the guard `g`.
* For example, the guard `g` might be a call `isSafe(x)` and the expression `e`
* the argument `x`.
*/
signature predicate guardChecksSig(Node g, Expr e, boolean branch, P param);
}

/**
* Provides a set of barrier nodes for a guard that validates an expression.
*
* This is expected to be used in `isBarrier`/`isSanitizer` definitions
* in data flow and taint tracking.
*/
module BarrierGuard<guardChecksSig/3 guardChecks> {
private predicate guardChecks(Node g, Expr e, boolean branch, Unit param) {
guardChecks(g, e, branch) and exists(param)
}

private module B = ParameterizedBarrierGuard<Unit, guardChecks/4>;

/** Gets a node that is safely guarded by the given guard check. */
Node getABarrierNode() { result = B::getABarrierNode(_) }

/**
* Gets a node that is safely guarded by the given guard check.
*/
Node getABarrierNodeForGuard(Node guardCheck) {
result = B::getABarrierNodeForGuard(guardCheck, _)
}
}

/**
* Provides a set of barrier nodes for a guard that validates an expression.
*
* This is expected to be used in `isBarrier`/`isSanitizer` definitions
* in data flow and taint tracking.
*/
module ParameterizedBarrierGuard<ParamSig P, WithParam<P>::guardChecksSig/4 guardChecks> {
/** Gets a node that is safely guarded by the given guard check. */
Node getABarrierNode() {
Node getABarrierNode(P param) {
exists(ControlFlow::ConditionGuardNode guard, SsaWithFields var |
result = pragma[only_bind_out](var).getAUse()
|
guards(_, guard, _, var) and
guards(_, guard, _, var, param) and
pragma[only_bind_out](guard).dominates(result.getBasicBlock())
)
}

/**
* Gets a node that is safely guarded by the given guard check.
*/
Node getABarrierNodeForGuard(Node guardCheck) {
Node getABarrierNodeForGuard(Node guardCheck, P param) {
exists(ControlFlow::ConditionGuardNode guard, SsaWithFields var | result = var.getAUse() |
guards(guardCheck, guard, _, var) and
guards(guardCheck, guard, _, var, param) and
guard.dominates(result.getBasicBlock())
)
}
Expand All @@ -373,22 +411,24 @@
* This predicate exists to enforce a good join order in `getAGuardedNode`.
*/
pragma[noinline]
private predicate guards(Node g, ControlFlow::ConditionGuardNode guard, Node nd, SsaWithFields ap) {
guards(g, guard, nd) and nd = ap.getAUse()
private predicate guards(

Check warning

Code scanning / CodeQL

Missing QLDoc for parameter Warning

The QLDoc has no documentation for param, but the QLDoc mentions getAGuardedNode
Node g, ControlFlow::ConditionGuardNode guard, Node nd, SsaWithFields ap, P param
) {
guards(g, guard, nd, param) and nd = ap.getAUse()
}

/**
* Holds if `guard` marks a point in the control-flow graph where `g`
* is known to validate `nd`.
*/
private predicate guards(Node g, ControlFlow::ConditionGuardNode guard, Node nd) {
private predicate guards(Node g, ControlFlow::ConditionGuardNode guard, Node nd, P param) {
exists(boolean branch |
guardChecks(g, nd.asExpr(), branch) and
guardChecks(g, nd.asExpr(), branch, param) and
guard.ensures(g, branch)
)
or
exists(DataFlow::Property p, Node resNode, Node check, boolean outcome |
guardingCall(g, _, _, _, p, _, nd, resNode) and
guardingCall(g, _, _, _, p, _, nd, resNode, param) and
p.checkOn(check, outcome, resNode) and
guard.ensures(pragma[only_bind_into](check), outcome)
)
Expand All @@ -405,9 +445,9 @@
pragma[noinline]
private predicate guardingCall(
Node g, Function f, FunctionInput inp, FunctionOutput outp, DataFlow::Property p, CallNode c,
Node nd, Node resNode
Node nd, Node resNode, P param
) {
guardingFunction(g, f, inp, outp, p) and
guardingFunction(g, f, inp, outp, p, param) and
c = f.getACall() and
nd = getInputNode(inp, c) and
localFlow(getOutputNode(outp, c), resNode)
Expand Down Expand Up @@ -438,15 +478,15 @@
* `false`, `nil` or a non-`nil` value.)
*/
private predicate guardingFunction(
Node g, Function f, FunctionInput inp, FunctionOutput outp, DataFlow::Property p
Node g, Function f, FunctionInput inp, FunctionOutput outp, DataFlow::Property p, P param
) {
exists(FuncDecl fd, Node arg, Node ret |
fd.getFunction() = f and
localFlow(inp.getExitNode(fd), pragma[only_bind_out](arg)) and
(
// Case: a function like "if someBarrierGuard(arg) { return true } else { return false }"
exists(ControlFlow::ConditionGuardNode guard |
guards(g, pragma[only_bind_out](guard), arg) and
guards(g, pragma[only_bind_out](guard), arg, param) and
guard.dominates(pragma[only_bind_out](ret).getBasicBlock())
|
onlyPossibleReturnSatisfyingProperty(fd, outp, ret, p)
Expand All @@ -456,7 +496,7 @@
// or "return !someBarrierGuard(arg) && otherCond(...)"
exists(boolean outcome |
ret = getUniqueOutputNode(fd, outp) and
guardChecks(g, arg.asExpr(), outcome) and
guardChecks(g, arg.asExpr(), outcome, param) and
// This predicate's contract is (p holds of ret ==> arg is checked),
// (and we have (this has outcome ==> arg is checked))
// but p.checkOn(ret, outcome, this) gives us (ret has outcome ==> p holds of this),
Expand All @@ -471,7 +511,7 @@
DataFlow::Property outpProp
|
ret = getUniqueOutputNode(fd, outp) and
guardingFunction(g, f2, inp2, outp2, outpProp) and
guardingFunction(g, f2, inp2, outp2, outpProp, param) and
c = f2.getACall() and
arg = inp2.getNode(c) and
(
Expand Down
19 changes: 15 additions & 4 deletions go/ql/lib/semmle/go/dataflow/internal/FlowSummaryImpl.qll
Original file line number Diff line number Diff line change
Expand Up @@ -160,16 +160,27 @@ module SourceSinkInterpretationInput implements
}

predicate barrierElement(
Element n, string output, string kind, Public::Provenance provenance, string model
Element e, string output, string kind, Public::Provenance provenance, string model
) {
none()
exists(
string package, string type, boolean subtypes, string name, string signature, string ext
|
barrierModel(package, type, subtypes, name, signature, ext, output, kind, provenance, model) and
e = interpretElement(package, type, subtypes, name, signature, ext)
)
}

predicate barrierGuardElement(
Element n, string input, Public::AcceptingValue acceptingvalue, string kind,
Element e, string input, Public::AcceptingValue acceptingvalue, string kind,
Public::Provenance provenance, string model
) {
none()
exists(
string package, string type, boolean subtypes, string name, string signature, string ext
|
barrierGuardModel(package, type, subtypes, name, signature, ext, input, acceptingvalue, kind,
provenance, model) and
e = interpretElement(package, type, subtypes, name, signature, ext)
)
}

// Note that due to embedding, which is currently implemented via some
Expand Down
8 changes: 0 additions & 8 deletions go/ql/lib/semmle/go/frameworks/Beego.qll
Original file line number Diff line number Diff line change
Expand Up @@ -165,14 +165,6 @@ module Beego {
override string getAContentType() { none() }
}

private class HtmlQuoteSanitizer extends SharedXss::Sanitizer {
HtmlQuoteSanitizer() {
exists(DataFlow::CallNode c | c.getTarget().hasQualifiedName(packagePath(), "Htmlquote") |
this = c.getArgument(0)
)
}
}

private class UtilsTaintPropagators extends TaintTracking::FunctionModel {
UtilsTaintPropagators() { this.hasQualifiedName(utilsPackagePath(), "GetDisplayString") }

Expand Down
4 changes: 2 additions & 2 deletions go/ql/lib/semmle/go/frameworks/NoSQL.qll
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ module NoSql {
*/
abstract class Range extends DataFlow::Node { }

private class DefaultQueryString extends Range {
DefaultQueryString() {
private class ExternalQueryString extends Range {
ExternalQueryString() {
exists(DataFlow::ArgumentNode arg | sinkNode(arg, "nosql-injection") |
this = arg.getACorrespondingSyntacticArgument()
)
Expand Down
4 changes: 2 additions & 2 deletions go/ql/lib/semmle/go/frameworks/SQL.qll
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ module SQL {
*/
abstract class Range extends DataFlow::Node { }

private class DefaultQueryString extends Range {
DefaultQueryString() {
private class ExternalQueryString extends Range {
ExternalQueryString() {
exists(DataFlow::ArgumentNode arg | sinkNode(arg, "sql-injection") |
not arg instanceof DataFlow::ImplicitVarargsSlice and
this = arg
Expand Down
4 changes: 2 additions & 2 deletions go/ql/lib/semmle/go/frameworks/SystemCommandExecutors.qll
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@

import go

private class DefaultSystemCommandExecution extends SystemCommandExecution::Range,
private class ExternalSystemCommandExecution extends SystemCommandExecution::Range,
DataFlow::CallNode
{
DataFlow::ArgumentNode commandName;

DefaultSystemCommandExecution() {
ExternalSystemCommandExecution() {
sinkNode(commandName, "command-injection") and
this = commandName.getCall()
}
Expand Down
Loading
Loading