Merge pull request #20986 from aschackmull/java/mad-barriers

Java: Support for MaD barriers and barrier guards.
This commit is contained in:
Anders Schack-Mulligen
2025-12-12 07:53:53 +01:00
committed by GitHub
24 changed files with 469 additions and 128 deletions

View File

@@ -148,6 +148,19 @@ module SourceSinkInterpretationInput implements
) )
} }
predicate barrierElement(
Element n, string output, string kind, Public::Provenance provenance, string model
) {
none()
}
predicate barrierGuardElement(
Element n, string input, Public::AcceptingValue acceptingvalue, string kind,
Public::Provenance provenance, string model
) {
none()
}
private newtype TInterpretNode = private newtype TInterpretNode =
TElement_(Element n) or TElement_(Element n) or
TNode_(Node n) TNode_(Node n)

View File

@@ -235,6 +235,19 @@ module SourceSinkInterpretationInput implements
) )
} }
predicate barrierElement(
Element n, string output, string kind, Public::Provenance provenance, string model
) {
none()
}
predicate barrierGuardElement(
Element n, string input, Public::AcceptingValue acceptingvalue, string kind,
Public::Provenance provenance, string model
) {
none()
}
class SourceOrSinkElement = Element; class SourceOrSinkElement = Element;
private newtype TInterpretNode = private newtype TInterpretNode =

View File

@@ -163,6 +163,19 @@ module SourceSinkInterpretationInput implements
) )
} }
predicate barrierElement(
Element n, string output, string kind, Public::Provenance provenance, string model
) {
none()
}
predicate barrierGuardElement(
Element n, string input, Public::AcceptingValue acceptingvalue, string kind,
Public::Provenance provenance, string model
) {
none()
}
// Note that due to embedding, which is currently implemented via some // Note that due to embedding, which is currently implemented via some
// Methods having multiple qualified names, a given Method is liable to have // Methods having multiple qualified names, a given Method is liable to have
// more than one SourceOrSinkElement, one for each of the names it claims. // more than one SourceOrSinkElement, one for each of the names it claims.

View File

@@ -13,6 +13,14 @@ extensions:
pack: codeql/java-all pack: codeql/java-all
extensible: summaryModel extensible: summaryModel
data: [] data: []
- addsTo:
pack: codeql/java-all
extensible: barrierModel
data: []
- addsTo:
pack: codeql/java-all
extensible: barrierGuardModel
data: []
- addsTo: - addsTo:
pack: codeql/java-all pack: codeql/java-all
extensible: neutralModel extensible: neutralModel

View File

@@ -50,6 +50,12 @@ extensions:
- ["hudson", "FilePath", False, "readToString", "", "", "ReturnValue", "file", "manual"] - ["hudson", "FilePath", False, "readToString", "", "", "ReturnValue", "file", "manual"]
- ["hudson", "Plugin", True, "configure", "", "", "Parameter", "remote", "manual"] - ["hudson", "Plugin", True, "configure", "", "", "Parameter", "remote", "manual"]
- ["hudson", "Plugin", True, "newInstance", "", "", "Parameter", "remote", "manual"] - ["hudson", "Plugin", True, "newInstance", "", "", "Parameter", "remote", "manual"]
- addsTo:
pack: codeql/java-all
extensible: barrierModel
data:
- ["hudson", "Util", True, "escape", "(String)", "", "ReturnValue", "html-injection", "manual"]
# Not including xmlEscape because it only accounts for >, <, and &. It does not account for ", or ', which makes it an incomplete XSS sanitizer.
- addsTo: - addsTo:
pack: codeql/java-all pack: codeql/java-all
extensible: summaryModel extensible: summaryModel

View File

@@ -162,3 +162,8 @@ extensions:
extensible: sourceModel extensible: sourceModel
data: data:
- ["java.io", "FileInputStream", True, "FileInputStream", "", "", "Argument[this]", "file", "manual"] - ["java.io", "FileInputStream", True, "FileInputStream", "", "", "Argument[this]", "file", "manual"]
- addsTo:
pack: codeql/java-all
extensible: barrierModel
data:
- ["java.io", "File", True, "getName", "()", "", "ReturnValue", "path-injection", "manual"]

View File

@@ -34,6 +34,11 @@ extensions:
- ["java.net", "URLClassLoader", False, "URLClassLoader", "(URL[],ClassLoader)", "", "Argument[0]", "request-forgery", "manual"] - ["java.net", "URLClassLoader", False, "URLClassLoader", "(URL[],ClassLoader)", "", "Argument[0]", "request-forgery", "manual"]
- ["java.net", "URLClassLoader", False, "URLClassLoader", "(URL[])", "", "Argument[0]", "request-forgery", "manual"] - ["java.net", "URLClassLoader", False, "URLClassLoader", "(URL[])", "", "Argument[0]", "request-forgery", "manual"]
- ["java.net", "PasswordAuthentication", False, "PasswordAuthentication", "(String,char[])", "", "Argument[0]", "credentials-username", "hq-generated"] - ["java.net", "PasswordAuthentication", False, "PasswordAuthentication", "(String,char[])", "", "Argument[0]", "credentials-username", "hq-generated"]
- addsTo:
pack: codeql/java-all
extensible: barrierGuardModel
data:
- ["java.net", "URI", True, "isAbsolute", "()", "", "Argument[this]", "false", "request-forgery", "manual"]
- addsTo: - addsTo:
pack: codeql/java-all pack: codeql/java-all
extensible: summaryModel extensible: summaryModel

View File

@@ -12,6 +12,11 @@ extensions:
- ["java.util.regex", "Pattern", False, "split", "(CharSequence)", "", "Argument[this]", "regex-use[0]", "manual"] - ["java.util.regex", "Pattern", False, "split", "(CharSequence)", "", "Argument[this]", "regex-use[0]", "manual"]
- ["java.util.regex", "Pattern", False, "split", "(CharSequence,int)", "", "Argument[this]", "regex-use[0]", "manual"] - ["java.util.regex", "Pattern", False, "split", "(CharSequence,int)", "", "Argument[this]", "regex-use[0]", "manual"]
- ["java.util.regex", "Pattern", False, "splitAsStream", "(CharSequence)", "", "Argument[this]", "regex-use[0]", "manual"] - ["java.util.regex", "Pattern", False, "splitAsStream", "(CharSequence)", "", "Argument[this]", "regex-use[0]", "manual"]
- addsTo:
pack: codeql/java-all
extensible: barrierModel
data:
- ["java.util.regex", "Pattern", False, "quote", "(String)", "", "ReturnValue", "regex-use", "manual"]
- addsTo: - addsTo:
pack: codeql/java-all pack: codeql/java-all
extensible: summaryModel extensible: summaryModel

View File

@@ -1,6 +1,42 @@
extensions: extensions:
- addsTo:
pack: codeql/java-all
extensible: barrierGuardModel
data:
- ["org.owasp.esapi", "Validator", true, "isValidCreditCard", "", "", "Argument[1]", "true", "trust-boundary-violation", "manual"]
- ["org.owasp.esapi", "Validator", true, "isValidDate", "", "", "Argument[1]", "true", "trust-boundary-violation", "manual"]
- ["org.owasp.esapi", "Validator", true, "isValidDirectoryPath", "", "", "Argument[1]", "true", "trust-boundary-violation", "manual"]
- ["org.owasp.esapi", "Validator", true, "isValidDouble", "", "", "Argument[1]", "true", "trust-boundary-violation", "manual"]
- ["org.owasp.esapi", "Validator", true, "isValidFileContent", "", "", "Argument[1]", "true", "trust-boundary-violation", "manual"]
- ["org.owasp.esapi", "Validator", true, "isValidFileName", "", "", "Argument[1]", "true", "trust-boundary-violation", "manual"]
- ["org.owasp.esapi", "Validator", true, "isValidInput", "", "", "Argument[1]", "true", "trust-boundary-violation", "manual"]
- ["org.owasp.esapi", "Validator", true, "isValidInteger", "", "", "Argument[1]", "true", "trust-boundary-violation", "manual"]
- ["org.owasp.esapi", "Validator", true, "isValidListItem", "", "", "Argument[1]", "true", "trust-boundary-violation", "manual"]
- ["org.owasp.esapi", "Validator", true, "isValidNumber", "", "", "Argument[1]", "true", "trust-boundary-violation", "manual"]
- ["org.owasp.esapi", "Validator", true, "isValidPrintable", "", "", "Argument[1]", "true", "trust-boundary-violation", "manual"]
- ["org.owasp.esapi", "Validator", true, "isValidRedirectLocation", "", "", "Argument[1]", "true", "trust-boundary-violation", "manual"]
- ["org.owasp.esapi", "Validator", true, "isValidSafeHTML", "", "", "Argument[1]", "true", "trust-boundary-violation", "manual"]
- ["org.owasp.esapi", "Validator", true, "isValidURI", "", "", "Argument[1]", "true", "trust-boundary-violation", "manual"]
- addsTo:
pack: codeql/java-all
extensible: barrierModel
data:
- ["org.owasp.esapi", "Validator", true, "getValidCreditCard", "", "", "ReturnValue", "trust-boundary-violation", "manual"]
- ["org.owasp.esapi", "Validator", true, "getValidDate", "", "", "ReturnValue", "trust-boundary-violation", "manual"]
- ["org.owasp.esapi", "Validator", true, "getValidDirectoryPath", "", "", "ReturnValue", "trust-boundary-violation", "manual"]
- ["org.owasp.esapi", "Validator", true, "getValidDouble", "", "", "ReturnValue", "trust-boundary-violation", "manual"]
- ["org.owasp.esapi", "Validator", true, "getValidFileContent", "", "", "ReturnValue", "trust-boundary-violation", "manual"]
- ["org.owasp.esapi", "Validator", true, "getValidFileName", "", "", "ReturnValue", "trust-boundary-violation", "manual"]
- ["org.owasp.esapi", "Validator", true, "getValidInput", "", "", "ReturnValue", "trust-boundary-violation", "manual"]
- ["org.owasp.esapi", "Validator", true, "getValidInteger", "", "", "ReturnValue", "trust-boundary-violation", "manual"]
- ["org.owasp.esapi", "Validator", true, "getValidListItem", "", "", "ReturnValue", "trust-boundary-violation", "manual"]
- ["org.owasp.esapi", "Validator", true, "getValidNumber", "", "", "ReturnValue", "trust-boundary-violation", "manual"]
- ["org.owasp.esapi", "Validator", true, "getValidPrintable", "", "", "ReturnValue", "trust-boundary-violation", "manual"]
- ["org.owasp.esapi", "Validator", true, "getValidRedirectLocation", "", "", "ReturnValue", "trust-boundary-violation", "manual"]
- ["org.owasp.esapi", "Validator", true, "getValidSafeHTML", "", "", "ReturnValue", "trust-boundary-violation", "manual"]
- ["org.owasp.esapi", "Validator", true, "getValidURI", "", "", "ReturnValue", "trust-boundary-violation", "manual"]
- addsTo: - addsTo:
pack: codeql/java-all pack: codeql/java-all
extensible: summaryModel extensible: summaryModel
data: data:
- ["org.owasp.esapi", "Encoder", true, "encodeForHTML", "(String)", "", "Argument[0]", "ReturnValue", "taint", "manual"] - ["org.owasp.esapi", "Encoder", true, "encodeForHTML", "(String)", "", "Argument[0]", "ReturnValue", "taint", "manual"]

View File

@@ -91,6 +91,7 @@ module;
import java import java
private import semmle.code.java.dataflow.DataFlow::DataFlow private import semmle.code.java.dataflow.DataFlow::DataFlow
private import semmle.code.java.controlflow.Guards
private import FlowSummary as FlowSummary private import FlowSummary as FlowSummary
private import internal.DataFlowPrivate private import internal.DataFlowPrivate
private import internal.FlowSummaryImpl private import internal.FlowSummaryImpl
@@ -174,6 +175,24 @@ predicate sinkModel(
) )
} }
/** Holds if a barrier model exists for the given parameters. */
predicate barrierModel(
string package, string type, boolean subtypes, string name, string signature, string ext,
string output, string kind, string provenance, QlBuiltins::ExtensionId madId
) {
Extensions::barrierModel(package, type, subtypes, name, signature, ext, output, kind, provenance,
madId)
}
/** Holds if a barrier guard model exists for the given parameters. */
predicate barrierGuardModel(
string package, string type, boolean subtypes, string name, string signature, string ext,
string input, string acceptingvalue, string kind, string provenance, QlBuiltins::ExtensionId madId
) {
Extensions::barrierGuardModel(package, type, subtypes, name, signature, ext, input,
acceptingvalue, kind, provenance, madId)
}
/** Holds if a summary model exists for the given parameters. */ /** Holds if a summary model exists for the given parameters. */
predicate summaryModel( predicate summaryModel(
string package, string type, boolean subtypes, string name, string signature, string ext, string package, string type, boolean subtypes, string name, string signature, string ext,
@@ -234,6 +253,7 @@ predicate interpretModelForTest(QlBuiltins::ExtensionId madId, string model) {
"Summary: " + package + "; " + type + "; " + subtypes + "; " + name + "; " + signature + "; " + "Summary: " + package + "; " + type + "; " + subtypes + "; " + name + "; " + signature + "; " +
ext + "; " + input + "; " + output + "; " + kind + "; " + provenance ext + "; " + input + "; " + output + "; " + kind + "; " + provenance
) )
//TODO: possibly barrier models?
} }
/** Holds if a neutral model exists for the given parameters. */ /** Holds if a neutral model exists for the given parameters. */
@@ -292,6 +312,7 @@ predicate modelCoverage(string package, int pkgs, string kind, string part, int
summaryModel(subpkg, type, subtypes, name, signature, ext, input, output, kind, provenance, summaryModel(subpkg, type, subtypes, name, signature, ext, input, output, kind, provenance,
_) _)
) )
// TODO: possibly barrier models?
) )
} }
@@ -303,7 +324,9 @@ module ModelValidation {
summaryModel(_, _, _, _, _, _, path, _, _, _, _) or summaryModel(_, _, _, _, _, _, path, _, _, _, _) or
summaryModel(_, _, _, _, _, _, _, path, _, _, _) or summaryModel(_, _, _, _, _, _, _, path, _, _, _) or
sinkModel(_, _, _, _, _, _, path, _, _, _) or sinkModel(_, _, _, _, _, _, path, _, _, _) or
sourceModel(_, _, _, _, _, _, path, _, _, _) sourceModel(_, _, _, _, _, _, path, _, _, _) or
barrierModel(_, _, _, _, _, _, path, _, _, _) or
barrierGuardModel(_, _, _, _, _, _, path, _, _, _, _)
} }
private module MkAccessPath = AccessPathSyntax::AccessPath<getRelevantAccessPath/1>; private module MkAccessPath = AccessPathSyntax::AccessPath<getRelevantAccessPath/1>;
@@ -316,6 +339,8 @@ module ModelValidation {
exists(string pred, AccessPath input, AccessPathToken part | exists(string pred, AccessPath input, AccessPathToken part |
sinkModel(_, _, _, _, _, _, input, _, _, _) and pred = "sink" sinkModel(_, _, _, _, _, _, input, _, _, _) and pred = "sink"
or or
barrierGuardModel(_, _, _, _, _, _, input, _, _, _, _) and pred = "barrier guard"
or
summaryModel(_, _, _, _, _, _, input, _, _, _, _) and pred = "summary" summaryModel(_, _, _, _, _, _, input, _, _, _, _) and pred = "summary"
| |
( (
@@ -338,6 +363,8 @@ module ModelValidation {
exists(string pred, AccessPath output, AccessPathToken part | exists(string pred, AccessPath output, AccessPathToken part |
sourceModel(_, _, _, _, _, _, output, _, _, _) and pred = "source" sourceModel(_, _, _, _, _, _, output, _, _, _) and pred = "source"
or or
barrierModel(_, _, _, _, _, _, output, _, _, _) and pred = "barrier"
or
summaryModel(_, _, _, _, _, _, _, output, _, _, _) and pred = "summary" summaryModel(_, _, _, _, _, _, _, output, _, _, _) and pred = "summary"
| |
( (
@@ -355,7 +382,13 @@ module ModelValidation {
private module KindValConfig implements SharedModelVal::KindValidationConfigSig { private module KindValConfig implements SharedModelVal::KindValidationConfigSig {
predicate summaryKind(string kind) { summaryModel(_, _, _, _, _, _, _, _, kind, _, _) } predicate summaryKind(string kind) { summaryModel(_, _, _, _, _, _, _, _, kind, _, _) }
predicate sinkKind(string kind) { sinkModel(_, _, _, _, _, _, _, kind, _, _) } predicate sinkKind(string kind) {
sinkModel(_, _, _, _, _, _, _, kind, _, _)
or
barrierModel(_, _, _, _, _, _, _, kind, _, _)
or
barrierGuardModel(_, _, _, _, _, _, _, _, kind, _, _)
}
predicate sourceKind(string kind) { sourceModel(_, _, _, _, _, _, _, kind, _, _) } predicate sourceKind(string kind) { sourceModel(_, _, _, _, _, _, _, kind, _, _) }
@@ -373,6 +406,11 @@ module ModelValidation {
or or
sinkModel(package, type, _, name, signature, ext, _, _, provenance, _) and pred = "sink" sinkModel(package, type, _, name, signature, ext, _, _, provenance, _) and pred = "sink"
or or
barrierModel(package, type, _, name, signature, ext, _, _, provenance, _) and pred = "barrier"
or
barrierGuardModel(package, type, _, name, signature, ext, _, _, _, provenance, _) and
pred = "barrier guard"
or
summaryModel(package, type, _, name, signature, ext, _, _, _, provenance, _) and summaryModel(package, type, _, name, signature, ext, _, _, _, provenance, _) and
pred = "summary" pred = "summary"
or or
@@ -398,6 +436,14 @@ module ModelValidation {
invalidProvenance(provenance) and invalidProvenance(provenance) and
result = "Unrecognized provenance description \"" + provenance + "\" in " + pred + " model." result = "Unrecognized provenance description \"" + provenance + "\" in " + pred + " model."
) )
or
exists(string acceptingvalue |
barrierGuardModel(_, _, _, _, _, _, _, acceptingvalue, _, _, _) and
invalidAcceptingValue(acceptingvalue) and
result =
"Unrecognized accepting value description \"" + acceptingvalue +
"\" in barrier guard model."
)
} }
/** Holds if some row in a MaD flow model appears to contain typos. */ /** Holds if some row in a MaD flow model appears to contain typos. */
@@ -418,6 +464,10 @@ private predicate elementSpec(
or or
sinkModel(package, type, subtypes, name, signature, ext, _, _, _, _) sinkModel(package, type, subtypes, name, signature, ext, _, _, _, _)
or or
barrierModel(package, type, subtypes, name, signature, ext, _, _, _, _)
or
barrierGuardModel(package, type, subtypes, name, signature, ext, _, _, _, _, _)
or
summaryModel(package, type, subtypes, name, signature, ext, _, _, _, _, _) summaryModel(package, type, subtypes, name, signature, ext, _, _, _, _, _)
or or
neutralModel(package, type, name, signature, _, _) and ext = "" and subtypes = true neutralModel(package, type, name, signature, _, _) and ext = "" and subtypes = true
@@ -578,6 +628,53 @@ private module Cached {
isSinkNode(n, kind, model) and n.asNode() = node isSinkNode(n, kind, model) and n.asNode() = node
) )
} }
private newtype TKindModelPair =
TMkPair(string kind, string model) { isBarrierGuardNode(_, _, kind, model) }
private GuardValue convertAcceptingValue(AcceptingValue av) {
av.isTrue() and result.asBooleanValue() = true
or
av.isFalse() and result.asBooleanValue() = false
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(Guard g, Expr e, GuardValue gv, TKindModelPair kmp) {
exists(
SourceSinkInterpretationInput::InterpretNode n, 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.(Call).getAnArgument() = e or g.(MethodCall).getQualifier() = e
)
}
/**
* Holds if `node` is specified as a barrier with the given kind in a MaD flow
* model.
*/
cached
predicate barrierNode(Node node, string kind, string model) {
exists(SourceSinkInterpretationInput::InterpretNode n |
isBarrierNode(n, kind, model) and n.asNode() = node
)
or
ParameterizedBarrierGuard<TKindModelPair, barrierGuardChecks/4>::getABarrierNode(TMkPair(kind,
model)) = node
}
} }
import Cached import Cached
@@ -594,6 +691,12 @@ predicate sourceNode(Node node, string kind) { sourceNode(node, kind, _) }
*/ */
predicate sinkNode(Node node, string kind) { sinkNode(node, kind, _) } predicate sinkNode(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(Node node, string kind) { barrierNode(node, kind, _) }
// adapter class for converting Mad summaries to `SummarizedCallable`s // adapter class for converting Mad summaries to `SummarizedCallable`s
private class SummarizedCallableAdapter extends SummarizedCallable { private class SummarizedCallableAdapter extends SummarizedCallable {
SummarizedCallableAdapter() { summaryElement(this, _, _, _, _, _, _) } SummarizedCallableAdapter() { summaryElement(this, _, _, _, _, _, _) }

View File

@@ -420,3 +420,31 @@ module BarrierGuard<guardChecksSig/3 guardChecks> {
/** Gets a node that is safely guarded by the given guard check. */ /** Gets a node that is safely guarded by the given guard check. */
Node getABarrierNode() { result = BarrierGuardValue<guardChecks0/3>::getABarrierNode() } Node getABarrierNode() { result = BarrierGuardValue<guardChecks0/3>::getABarrierNode() }
} }
bindingset[this]
private signature class ParamSig;
private module WithParam<ParamSig P> {
/**
* Holds if the guard `g` validates the expression `e` upon evaluating to `gv`.
*
* 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(Guard g, Expr e, GuardValue gv, 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 ParameterizedBarrierGuard<ParamSig P, WithParam<P>::guardChecksSig/4 guardChecks> {
/** Gets a node that is safely guarded by the given guard check. */
Node getABarrierNode(P param) {
SsaFlow::asNode(result) =
SsaImpl::DataFlowIntegration::ParameterizedBarrierGuard<P, guardChecks/4>::getABarrierNode(param)
}
}

View File

@@ -20,6 +20,22 @@ extensible predicate sinkModel(
string input, string kind, string provenance, QlBuiltins::ExtensionId madId string input, string kind, string provenance, QlBuiltins::ExtensionId madId
); );
/**
* Holds if a barrier model exists for the given parameters.
*/
extensible predicate barrierModel(
string package, string type, boolean subtypes, string name, string signature, string ext,
string output, string kind, string provenance, QlBuiltins::ExtensionId madId
);
/**
* Holds if a barrier guard model exists for the given parameters.
*/
extensible predicate barrierGuardModel(
string package, string type, boolean subtypes, string name, string signature, string ext,
string input, string acceptingvalue, string kind, string provenance, QlBuiltins::ExtensionId madId
);
/** /**
* Holds if a summary model exists for the given parameters. * Holds if a summary model exists for the given parameters.
*/ */

View File

@@ -158,7 +158,9 @@ private predicate relatedArgSpec(Callable c, string spec) {
summaryModel(namespace, type, subtypes, name, signature, ext, spec, _, _, _, _) or summaryModel(namespace, type, subtypes, name, signature, ext, spec, _, _, _, _) or
summaryModel(namespace, type, subtypes, name, signature, ext, _, spec, _, _, _) or summaryModel(namespace, type, subtypes, name, signature, ext, _, spec, _, _, _) or
sourceModel(namespace, type, subtypes, name, signature, ext, spec, _, _, _) or sourceModel(namespace, type, subtypes, name, signature, ext, spec, _, _, _) or
sinkModel(namespace, type, subtypes, name, signature, ext, spec, _, _, _) sinkModel(namespace, type, subtypes, name, signature, ext, spec, _, _, _) or
barrierModel(namespace, type, subtypes, name, signature, ext, spec, _, _, _) or
barrierGuardModel(namespace, type, subtypes, name, signature, ext, spec, _, _, _, _)
| |
c = interpretElement(namespace, type, subtypes, name, signature, ext, _) c = interpretElement(namespace, type, subtypes, name, signature, ext, _)
) )
@@ -259,6 +261,45 @@ module SourceSinkInterpretationInput implements
) )
} }
predicate barrierElement(
Element e, string output, string kind, Public::Provenance provenance, string model
) {
exists(
string namespace, string type, boolean subtypes, string name, string signature, string ext,
SourceOrSinkElement baseBarrier, string originalOutput, QlBuiltins::ExtensionId madId
|
barrierModel(namespace, type, subtypes, name, signature, ext, originalOutput, kind,
provenance, madId) and
model = "MaD:" + madId.toString() and
baseBarrier = interpretElement(namespace, type, subtypes, name, signature, ext, _) and
(
e = baseBarrier and output = originalOutput
or
correspondingKotlinParameterDefaultsArgSpec(baseBarrier, e, originalOutput, output)
)
)
}
predicate barrierGuardElement(
Element e, string input, Public::AcceptingValue acceptingvalue, string kind,
Public::Provenance provenance, string model
) {
exists(
string namespace, string type, boolean subtypes, string name, string signature, string ext,
SourceOrSinkElement baseBarrier, string originalInput, QlBuiltins::ExtensionId madId
|
barrierGuardModel(namespace, type, subtypes, name, signature, ext, originalInput,
acceptingvalue, kind, provenance, madId) and
model = "MaD:" + madId.toString() and
baseBarrier = interpretElement(namespace, type, subtypes, name, signature, ext, _) and
(
e = baseBarrier and input = originalInput
or
correspondingKotlinParameterDefaultsArgSpec(baseBarrier, e, originalInput, input)
)
)
}
class SourceOrSinkElement = Element; class SourceOrSinkElement = Element;
private newtype TInterpretNode = private newtype TInterpretNode =

View File

@@ -588,6 +588,36 @@ private module Cached {
predicate getABarrierNode = getABarrierNodeImpl/0; predicate getABarrierNode = getABarrierNodeImpl/0;
} }
bindingset[this]
private signature class ParamSig;
private module WithParam<ParamSig P> {
signature predicate guardChecksSig(Guards::Guard g, Expr e, Guards::GuardValue gv, P param);
}
cached // nothing is actually cached
module ParameterizedBarrierGuard<ParamSig P, WithParam<P>::guardChecksSig/4 guardChecks> {
private predicate guardChecksAdjTypes(
Guards::Guards_v3::Guard g, Expr e, Guards::GuardValue gv, P param
) {
guardChecks(g, e, gv, param)
}
private predicate guardChecksWithWrappers(
DataFlowIntegrationInput::Guard g, Definition def, Guards::GuardValue val, P param
) {
Guards::Guards_v3::ParameterizedValidationWrapper<P, guardChecksAdjTypes/4>::guardChecksDef(g,
def, val, param)
}
private Node getABarrierNodeImpl(P param) {
result =
DataFlowIntegrationImpl::BarrierGuardDefWithState<P, guardChecksWithWrappers/4>::getABarrierNode(param)
}
predicate getABarrierNode = getABarrierNodeImpl/1;
}
} }
cached cached

View File

@@ -14,14 +14,3 @@ class HudsonWebMethod extends Method {
this.getDeclaringType().getASourceSupertype*().hasQualifiedName("hudson.model", "Descriptor") this.getDeclaringType().getASourceSupertype*().hasQualifiedName("hudson.model", "Descriptor")
} }
} }
private class HudsonUtilXssSanitizer extends XssSanitizer {
HudsonUtilXssSanitizer() {
this.asExpr()
.(MethodCall)
.getMethod()
// Not including xmlEscape because it only accounts for >, <, and &.
// It does not account for ", or ', which makes it an incomplete XSS sanitizer.
.hasQualifiedName("hudson", "Util", "escape")
}
}

View File

@@ -1,42 +0,0 @@
/** Classes and predicates for reasoning about the `owasp.easpi` package. */
overlay[local?]
module;
import java
/**
* The `org.owasp.esapi.Validator` interface.
*/
class EsapiValidator extends RefType {
EsapiValidator() { this.hasQualifiedName("org.owasp.esapi", "Validator") }
}
/**
* The methods of `org.owasp.esapi.Validator` which validate data.
*/
class EsapiIsValidMethod extends Method {
EsapiIsValidMethod() {
this.getDeclaringType() instanceof EsapiValidator and
this.hasName([
"isValidCreditCard", "isValidDate", "isValidDirectoryPath", "isValidDouble",
"isValidFileContent", "isValidFileName", "isValidInput", "isValidInteger",
"isValidListItem", "isValidNumber", "isValidPrintable", "isValidRedirectLocation",
"isValidSafeHTML", "isValidURI"
])
}
}
/**
* The methods of `org.owasp.esapi.Validator` which return validated data.
*/
class EsapiGetValidMethod extends Method {
EsapiGetValidMethod() {
this.getDeclaringType() instanceof EsapiValidator and
this.hasName([
"getValidCreditCard", "getValidDate", "getValidDirectoryPath", "getValidDouble",
"getValidFileContent", "getValidFileName", "getValidInput", "getValidInteger",
"getValidListItem", "getValidNumber", "getValidPrintable", "getValidRedirectLocation",
"getValidSafeHTML", "getValidURI"
])
}
}

View File

@@ -4,6 +4,7 @@ module;
import java import java
private import semmle.code.java.controlflow.Guards private import semmle.code.java.controlflow.Guards
private import semmle.code.java.dataflow.ExternalFlow
private import semmle.code.java.dataflow.FlowSources private import semmle.code.java.dataflow.FlowSources
private import semmle.code.java.dataflow.SSA private import semmle.code.java.dataflow.SSA
private import semmle.code.java.frameworks.kotlin.IO private import semmle.code.java.frameworks.kotlin.IO
@@ -288,19 +289,8 @@ private Method getSourceMethod(Method m) {
result = m result = m
} }
/** private class DefaultPathInjectionSanitizer extends PathInjectionSanitizer {
* A sanitizer that protects against path injection vulnerabilities DefaultPathInjectionSanitizer() { barrierNode(this, "path-injection") }
* by extracting the final component of the user provided path.
*
* TODO: convert this class to models-as-data if sanitizer support is added
*/
private class FileGetNameSanitizer extends PathInjectionSanitizer {
FileGetNameSanitizer() {
exists(MethodCall mc |
mc.getMethod().hasQualifiedName("java.io", "File", "getName") and
this.asExpr() = mc
)
}
} }
/** Holds if `g` is a guard that checks for `..` components. */ /** Holds if `g` is a guard that checks for `..` components. */

View File

@@ -118,25 +118,8 @@ private class ContainsUrlSanitizer extends RequestForgerySanitizer {
} }
} }
/** private class DefaultRequestForgerySanitizer extends RequestForgerySanitizer {
* A check that the URL is relative, and therefore safe for URL redirects. DefaultRequestForgerySanitizer() { barrierNode(this, "request-forgery") }
*/
private predicate isRelativeUrlSanitizer(Guard guard, Expr e, boolean branch) {
guard =
any(MethodCall call |
call.getMethod().hasQualifiedName("java.net", "URI", "isAbsolute") and
e = call.getQualifier() and
branch = false
)
}
/**
* A check that the URL is relative, and therefore safe for URL redirects.
*/
private class RelativeUrlSanitizer extends RequestForgerySanitizer {
RelativeUrlSanitizer() {
this = DataFlow::BarrierGuard<isRelativeUrlSanitizer/3>::getABarrierNode()
}
} }
/** /**

View File

@@ -5,7 +5,6 @@ private import semmle.code.java.dataflow.DataFlow
private import semmle.code.java.controlflow.Guards private import semmle.code.java.controlflow.Guards
private import semmle.code.java.dataflow.ExternalFlow private import semmle.code.java.dataflow.ExternalFlow
private import semmle.code.java.dataflow.FlowSources private import semmle.code.java.dataflow.FlowSources
private import semmle.code.java.frameworks.owasp.Esapi
private import semmle.code.java.security.Sanitizers private import semmle.code.java.security.Sanitizers
/** /**
@@ -28,25 +27,8 @@ class TrustBoundaryViolationSink extends DataFlow::Node {
*/ */
abstract class TrustBoundaryValidationSanitizer extends DataFlow::Node { } abstract class TrustBoundaryValidationSanitizer extends DataFlow::Node { }
/** private class DefaultTrustBoundaryValidationSanitizer extends TrustBoundaryValidationSanitizer {
* A node validated by an OWASP ESAPI validation method. DefaultTrustBoundaryValidationSanitizer() { barrierNode(this, "trust-boundary-violation") }
*/
private class EsapiValidatedInputSanitizer extends TrustBoundaryValidationSanitizer {
EsapiValidatedInputSanitizer() {
this = DataFlow::BarrierGuard<esapiIsValidData/3>::getABarrierNode() or
this.asExpr().(MethodCall).getMethod() instanceof EsapiGetValidMethod
}
}
/**
* Holds if `g` is a guard that checks that `e` is valid data according to an OWASP ESAPI validation method.
*/
private predicate esapiIsValidData(Guard g, Expr e, boolean branch) {
branch = true and
exists(MethodCall ma | ma.getMethod() instanceof EsapiIsValidMethod |
g = ma and
e = ma.getArgument(1)
)
} }
/** /**

View File

@@ -54,12 +54,24 @@ private class DefaultXssSink extends XssSink {
} }
} }
/** A default sanitizer that considers numeric and boolean typed data safe for writing to output. */
private class DefaultXssSanitizer extends XssSanitizer { private class DefaultXssSanitizer extends XssSanitizer {
DefaultXssSanitizer() { DefaultXssSanitizer() { barrierNode(this, ["html-injection", "js-injection"]) }
}
/** A sanitizer that considers numeric and boolean typed data safe for writing to output. */
private class PrimitiveSanitizer extends XssSanitizer {
PrimitiveSanitizer() {
this.getType() instanceof NumericType or this.getType() instanceof NumericType or
this.getType() instanceof BooleanType or this.getType() instanceof BooleanType
// Match `org.springframework.web.util.HtmlUtils.htmlEscape` and possibly other methods like it. }
}
/**
* A call to `org.springframework.web.util.HtmlUtils.htmlEscape`, or possibly
* other methods like it, considered as a sanitizer for XSS.
*/
private class HtmlEscapeXssSanitizer extends XssSanitizer {
HtmlEscapeXssSanitizer() {
this.asExpr().(MethodCall).getMethod().getName().regexpMatch("(?i)html_?escape.*") this.asExpr().(MethodCall).getMethod().getName().regexpMatch("(?i)html_?escape.*")
} }
} }

View File

@@ -21,17 +21,8 @@ private class DefaultRegexInjectionSink extends RegexInjectionSink {
} }
} }
/** private class DefaultRegexInjectionSanitizer extends RegexInjectionSanitizer {
* A call to the `Pattern.quote` method, which gives metacharacters or escape sequences DefaultRegexInjectionSanitizer() { barrierNode(this, "regex-use") }
* no special meaning.
*/
private class PatternQuoteCall extends RegexInjectionSanitizer {
PatternQuoteCall() {
exists(MethodCall ma, Method m | m = ma.getMethod() |
ma.getArgument(0) = this.asExpr() and
m instanceof PatternQuoteMethod
)
}
} }
/** /**

View File

@@ -215,6 +215,35 @@ module Make<
] ]
} }
class AcceptingValue extends string {
AcceptingValue() {
this =
[
"true",
"false",
"no-exception",
"zero",
"not-zero",
"null",
"not-null",
]
}
predicate isTrue() { this = "true" }
predicate isFalse() { this = "false" }
predicate isNoException() { this = "no-exception" }
predicate isZero() { this = "zero" }
predicate isNotZero() { this = "not-zero" }
predicate isNull() { this = "null" }
predicate isNotNull() { this = "not-null" }
}
/** /**
* A class used to represent provenance values for MaD models. * A class used to represent provenance values for MaD models.
* *
@@ -2015,6 +2044,12 @@ module Make<
not exists(interpretComponent(c)) not exists(interpretComponent(c))
} }
/** Holds if `acceptingvalue` is not a valid barrier guard accepting-value. */
bindingset[acceptingvalue]
predicate invalidAcceptingValue(string acceptingvalue) {
not acceptingvalue instanceof AcceptingValue
}
/** Holds if `provenance` is not a valid provenance value. */ /** Holds if `provenance` is not a valid provenance value. */
bindingset[provenance] bindingset[provenance]
predicate invalidProvenance(string provenance) { not provenance instanceof Provenance } predicate invalidProvenance(string provenance) { not provenance instanceof Provenance }
@@ -2052,6 +2087,23 @@ module Make<
Element n, string input, string kind, Provenance provenance, string model Element n, string input, string kind, Provenance provenance, string model
); );
/**
* Holds if an external barrier specification exists for `n` with output specification
* `output` and kind `kind`.
*/
predicate barrierElement(
Element n, string output, string kind, Provenance provenance, string model
);
/**
* Holds if an external barrier guard specification exists for `n` with input
* specification `input`, accepting value `acceptingvalue`, and kind `kind`.
*/
predicate barrierGuardElement(
Element n, string input, AcceptingValue acceptingvalue, string kind,
Provenance provenance, string model
);
class SourceOrSinkElement extends Element; class SourceOrSinkElement extends Element;
/** An entity used to interpret a source/sink specification. */ /** An entity used to interpret a source/sink specification. */
@@ -2105,7 +2157,9 @@ module Make<
private predicate sourceSinkSpec(string spec) { private predicate sourceSinkSpec(string spec) {
sourceElement(_, spec, _, _, _) or sourceElement(_, spec, _, _, _) or
sinkElement(_, spec, _, _, _) sinkElement(_, spec, _, _, _) or
barrierElement(_, spec, _, _, _) or
barrierGuardElement(_, spec, _, _, _, _)
} }
private module AccessPath = AccessPathSyntax::AccessPath<sourceSinkSpec/1>; private module AccessPath = AccessPathSyntax::AccessPath<sourceSinkSpec/1>;
@@ -2160,11 +2214,34 @@ module Make<
) )
} }
private predicate barrierElementRef(
InterpretNode ref, SourceSinkAccessPath output, string kind, string model
) {
exists(SourceOrSinkElement e |
barrierElement(e, output, kind, _, model) and
if outputNeedsReferenceExt(output.getToken(0))
then e = ref.getCallTarget()
else e = ref.asElement()
)
}
private predicate barrierGuardElementRef(
InterpretNode ref, SourceSinkAccessPath input, AcceptingValue acceptingvalue, string kind,
string model
) {
exists(SourceOrSinkElement e |
barrierGuardElement(e, input, acceptingvalue, kind, _, model) and
if inputNeedsReferenceExt(input.getToken(0))
then e = ref.getCallTarget()
else e = ref.asElement()
)
}
/** Holds if the first `n` tokens of `output` resolve to the given interpretation. */ /** Holds if the first `n` tokens of `output` resolve to the given interpretation. */
private predicate interpretOutput( private predicate interpretOutput(
SourceSinkAccessPath output, int n, InterpretNode ref, InterpretNode node SourceSinkAccessPath output, int n, InterpretNode ref, InterpretNode node
) { ) {
sourceElementRef(ref, output, _, _) and (sourceElementRef(ref, output, _, _) or barrierElementRef(ref, output, _, _)) and
n = 0 and n = 0 and
( (
if output = "" if output = ""
@@ -2220,7 +2297,7 @@ module Make<
private predicate interpretInput( private predicate interpretInput(
SourceSinkAccessPath input, int n, InterpretNode ref, InterpretNode node SourceSinkAccessPath input, int n, InterpretNode ref, InterpretNode node
) { ) {
sinkElementRef(ref, input, _, _) and (sinkElementRef(ref, input, _, _) or barrierGuardElementRef(ref, input, _, _, _)) and
n = 0 and n = 0 and
( (
if input = "" if input = ""
@@ -2280,6 +2357,30 @@ module Make<
) )
} }
/**
* Holds if `node` is specified as a barrier with the given kind in a MaD flow
* model.
*/
predicate isBarrierNode(InterpretNode node, string kind, string model) {
exists(InterpretNode ref, SourceSinkAccessPath output |
barrierElementRef(ref, output, kind, model) and
interpretOutput(output, output.getNumToken(), ref, node)
)
}
/**
* Holds if `node` is specified as a barrier guard argument with the
* given kind in a MaD flow model.
*/
predicate isBarrierGuardNode(
InterpretNode node, AcceptingValue acceptingvalue, string kind, string model
) {
exists(InterpretNode ref, SourceSinkAccessPath input |
barrierGuardElementRef(ref, input, acceptingvalue, kind, model) and
interpretInput(input, input.getNumToken(), ref, node)
)
}
final private class SourceOrSinkElementFinal = SourceOrSinkElement; final private class SourceOrSinkElementFinal = SourceOrSinkElement;
signature predicate sourceOrSinkElementSig( signature predicate sourceOrSinkElementSig(

View File

@@ -173,7 +173,7 @@ module KindValidation<KindValidationConfigSig Config> {
or or
exists(string kind, string msg | Config::sinkKind(kind) | exists(string kind, string msg | Config::sinkKind(kind) |
not kind instanceof ValidSinkKind and not kind instanceof ValidSinkKind and
msg = "Invalid kind \"" + kind + "\" in sink model." and msg = "Invalid kind \"" + kind + "\" in sink or barrier model." and
// The part of this message that refers to outdated sink kinds can be deleted after June 1st, 2024. // The part of this message that refers to outdated sink kinds can be deleted after June 1st, 2024.
if kind instanceof OutdatedSinkKind if kind instanceof OutdatedSinkKind
then result = msg + " " + kind.(OutdatedSinkKind).outdatedMessage() then result = msg + " " + kind.(OutdatedSinkKind).outdatedMessage()

View File

@@ -159,6 +159,19 @@ module SourceSinkInterpretationInput implements
) )
} }
predicate barrierElement(
Element n, string output, string kind, Public::Provenance provenance, string model
) {
none()
}
predicate barrierGuardElement(
Element n, string input, Public::AcceptingValue acceptingvalue, string kind,
Public::Provenance provenance, string model
) {
none()
}
private newtype TInterpretNode = private newtype TInterpretNode =
TElement_(Element n) or TElement_(Element n) or
TNode_(Node n) or TNode_(Node n) or