mirror of
https://github.com/github/codeql.git
synced 2025-12-17 01:03:14 +01:00
635 lines
20 KiB
Plaintext
635 lines
20 KiB
Plaintext
private import java
|
|
private import semmle.code.java.dataflow.DataFlow
|
|
private import semmle.code.java.Collections
|
|
private import semmle.code.java.dataflow.SSA
|
|
private import semmle.code.java.dataflow.DefUse
|
|
private import semmle.code.java.security.SecurityTests
|
|
private import semmle.code.java.security.Validation
|
|
private import semmle.code.java.Maps
|
|
private import semmle.code.java.dataflow.internal.ContainerFlow
|
|
private import semmle.code.java.frameworks.spring.SpringController
|
|
private import semmle.code.java.frameworks.spring.SpringHttp
|
|
private import semmle.code.java.frameworks.Networking
|
|
private import semmle.code.java.dataflow.FlowSources
|
|
private import semmle.code.java.dataflow.internal.DataFlowPrivate
|
|
import semmle.code.java.dataflow.FlowSteps
|
|
private import FlowSummaryImpl as FlowSummaryImpl
|
|
private import semmle.code.java.frameworks.JaxWS
|
|
|
|
/**
|
|
* Holds if taint can flow from `src` to `sink` in zero or more
|
|
* local (intra-procedural) steps.
|
|
*/
|
|
pragma[inline]
|
|
predicate localTaint(DataFlow::Node src, DataFlow::Node sink) { localTaintStep*(src, sink) }
|
|
|
|
/**
|
|
* Holds if taint can flow from `src` to `sink` in zero or more
|
|
* local (intra-procedural) steps.
|
|
*/
|
|
pragma[inline]
|
|
predicate localExprTaint(Expr src, Expr sink) {
|
|
localTaint(DataFlow::exprNode(src), DataFlow::exprNode(sink))
|
|
}
|
|
|
|
/** Holds if `node` is an endpoint for local taint flow. */
|
|
signature predicate nodeSig(DataFlow::Node node);
|
|
|
|
/** Provides local taint flow restricted to a given set of sources and sinks. */
|
|
module LocalTaintFlow<nodeSig/1 source, nodeSig/1 sink> {
|
|
private predicate reachRev(DataFlow::Node n) {
|
|
sink(n)
|
|
or
|
|
exists(DataFlow::Node mid |
|
|
localTaintStep(n, mid) and
|
|
reachRev(mid)
|
|
)
|
|
}
|
|
|
|
private predicate reachFwd(DataFlow::Node n) {
|
|
reachRev(n) and
|
|
(
|
|
source(n)
|
|
or
|
|
exists(DataFlow::Node mid |
|
|
localTaintStep(mid, n) and
|
|
reachFwd(mid)
|
|
)
|
|
)
|
|
}
|
|
|
|
private predicate step(DataFlow::Node n1, DataFlow::Node n2) {
|
|
localTaintStep(n1, n2) and
|
|
reachFwd(n1) and
|
|
reachFwd(n2)
|
|
}
|
|
|
|
/**
|
|
* Holds if taint can flow from `n1` to `n2` in zero or more local
|
|
* (intra-procedural) steps that are restricted to be part of a path between
|
|
* `source` and `sink`.
|
|
*/
|
|
pragma[inline]
|
|
predicate hasFlow(DataFlow::Node n1, DataFlow::Node n2) { step*(n1, n2) }
|
|
|
|
/**
|
|
* Holds if taint can flow from `n1` to `n2` in zero or more local
|
|
* (intra-procedural) steps that are restricted to be part of a path between
|
|
* `source` and `sink`.
|
|
*/
|
|
pragma[inline]
|
|
predicate hasExprFlow(Expr n1, Expr n2) {
|
|
hasFlow(DataFlow::exprNode(n1), DataFlow::exprNode(n2))
|
|
}
|
|
}
|
|
|
|
cached
|
|
private module Cached {
|
|
private import DataFlowImplCommon as DataFlowImplCommon
|
|
private import DataFlowPrivate as DataFlowPrivate
|
|
|
|
cached
|
|
predicate forceCachingInSameStage() { DataFlowImplCommon::forceCachingInSameStage() }
|
|
|
|
/**
|
|
* Holds if taint can flow in one local step from `src` to `sink`.
|
|
*/
|
|
cached
|
|
predicate localTaintStep(DataFlow::Node src, DataFlow::Node sink) {
|
|
DataFlow::localFlowStep(src, sink)
|
|
or
|
|
localAdditionalTaintStep(src, sink)
|
|
or
|
|
// Simple flow through library code is included in the exposed local
|
|
// step relation, even though flow is technically inter-procedural
|
|
FlowSummaryImpl::Private::Steps::summaryThroughStepTaint(src, sink, _)
|
|
or
|
|
// Treat container flow as taint for the local taint flow relation
|
|
exists(DataFlow::Content c | containerContent(c) |
|
|
readStep(src, c, sink) or
|
|
storeStep(src, c, sink) or
|
|
FlowSummaryImpl::Private::Steps::summaryGetterStep(src, c, sink, _) or
|
|
FlowSummaryImpl::Private::Steps::summarySetterStep(src, c, sink, _)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if taint can flow in one local step from `src` to `sink` excluding
|
|
* local data flow steps. That is, `src` and `sink` are likely to represent
|
|
* different objects.
|
|
*/
|
|
cached
|
|
predicate localAdditionalTaintStep(DataFlow::Node src, DataFlow::Node sink) {
|
|
localAdditionalTaintExprStep(src.asExpr(), sink.asExpr())
|
|
or
|
|
localAdditionalTaintUpdateStep(src.asExpr(),
|
|
sink.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr())
|
|
or
|
|
exists(DataFlow::Content f |
|
|
readStep(src, f, sink) and
|
|
not sink.getTypeBound() instanceof PrimitiveType and
|
|
not sink.getTypeBound() instanceof BoxedType and
|
|
not sink.getTypeBound() instanceof NumberType and
|
|
(
|
|
containerContent(f)
|
|
or
|
|
f instanceof TaintInheritingContent
|
|
)
|
|
)
|
|
or
|
|
FlowSummaryImpl::Private::Steps::summaryLocalStep(src.(DataFlowPrivate::FlowSummaryNode)
|
|
.getSummaryNode(), sink.(DataFlowPrivate::FlowSummaryNode).getSummaryNode(), false)
|
|
}
|
|
|
|
/**
|
|
* Holds if the additional step from `src` to `sink` should be included in all
|
|
* global taint flow configurations.
|
|
*/
|
|
cached
|
|
predicate defaultAdditionalTaintStep(DataFlow::Node src, DataFlow::Node sink) {
|
|
localAdditionalTaintStep(src, sink) or
|
|
entrypointFieldStep(src, sink) or
|
|
any(AdditionalTaintStep a).step(src, sink)
|
|
}
|
|
|
|
/**
|
|
* Holds if `node` should be a sanitizer in all global taint flow configurations
|
|
* but not in local taint.
|
|
*/
|
|
cached
|
|
predicate defaultTaintSanitizer(DataFlow::Node node) {
|
|
// Ignore paths through test code.
|
|
node.getEnclosingCallable().getDeclaringType() instanceof NonSecurityTestClass or
|
|
node.asExpr() instanceof ValidatedVariableAccess
|
|
}
|
|
}
|
|
|
|
import Cached
|
|
|
|
private RefType getElementType(RefType container) {
|
|
result = container.(Array).getComponentType() or
|
|
result = container.(CollectionType).getElementType() or
|
|
result = container.(MapType).getValueType()
|
|
}
|
|
|
|
/**
|
|
* Holds if default `TaintTracking::Configuration`s should allow implicit reads
|
|
* of `c` at sinks and inputs to additional taint steps.
|
|
*/
|
|
bindingset[node]
|
|
predicate defaultImplicitTaintRead(DataFlow::Node node, DataFlow::ContentSet c) {
|
|
exists(RefType container |
|
|
(node.asExpr() instanceof Argument or node instanceof ArgumentNode) and
|
|
getElementType*(node.getType()) = container
|
|
|
|
|
container instanceof Array and
|
|
c instanceof DataFlow::ArrayContent
|
|
or
|
|
container instanceof CollectionType and
|
|
c instanceof DataFlow::CollectionContent
|
|
or
|
|
container instanceof MapType and
|
|
c instanceof DataFlow::MapValueContent
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if taint can flow in one local step from `src` to `sink` excluding
|
|
* local data flow steps. That is, `src` and `sink` are likely to represent
|
|
* different objects.
|
|
*/
|
|
private predicate localAdditionalTaintExprStep(Expr src, Expr sink) {
|
|
sink.(AddExpr).getAnOperand() = src and sink.getType() instanceof TypeString
|
|
or
|
|
sink.(AssignAddExpr).getSource() = src and sink.getType() instanceof TypeString
|
|
or
|
|
sink.(StringTemplateExpr).getComponent(_) = src
|
|
or
|
|
sink.(LogicExpr).getAnOperand() = src
|
|
or
|
|
constructorStep(src, sink)
|
|
or
|
|
qualifierToMethodStep(src, sink)
|
|
or
|
|
argToMethodStep(src, sink)
|
|
or
|
|
comparisonStep(src, sink)
|
|
or
|
|
serializationStep(src, sink)
|
|
or
|
|
formatStep(src, sink)
|
|
}
|
|
|
|
/**
|
|
* Holds if taint can flow in one local step from `src` to `sink` excluding
|
|
* local data flow steps. That is, `src` and `sink` are likely to represent
|
|
* different objects.
|
|
* This is restricted to cases where the step updates the value of `sink`.
|
|
*/
|
|
private predicate localAdditionalTaintUpdateStep(Expr src, Expr sink) {
|
|
qualifierToArgumentStep(src, sink)
|
|
or
|
|
argToArgStep(src, sink)
|
|
or
|
|
argToQualifierStep(src, sink)
|
|
}
|
|
|
|
private class BulkData extends RefType {
|
|
BulkData() {
|
|
this.(Array).getElementType().(PrimitiveType).hasName(["byte", "char"])
|
|
or
|
|
exists(RefType t | this.getASourceSupertype*() = t |
|
|
t instanceof TypeInputStream or
|
|
t.hasQualifiedName("java.nio", "ByteBuffer") or
|
|
t.hasQualifiedName("java.lang", "Readable") or
|
|
t.hasQualifiedName("java.io", "DataInput") or
|
|
t.hasQualifiedName("java.nio.channels", "ReadableByteChannel")
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Holds if `c` is a constructor for a subclass of `java.io.InputStream` that
|
|
* wraps an underlying data source. The underlying data source is given as a
|
|
* the `argi`'th parameter to the constructor.
|
|
*
|
|
* An object construction of such a wrapper is likely to preserve the data flow
|
|
* status of its argument.
|
|
*/
|
|
private predicate inputStreamWrapper(Constructor c, int argi) {
|
|
not c.fromSource() and
|
|
c.getParameterType(argi) instanceof BulkData and
|
|
c.getDeclaringType().getASourceSupertype+() instanceof TypeInputStream
|
|
}
|
|
|
|
/** An object construction that preserves the data flow status of any of its arguments. */
|
|
private predicate constructorStep(Expr tracked, ConstructorCall sink) {
|
|
exists(int argi | sink.getArgument(argi) = tracked |
|
|
// wrappers constructed by extension
|
|
exists(Constructor c, Parameter p, SuperConstructorInvocationStmt sup |
|
|
c = sink.getConstructor() and
|
|
p = c.getParameter(argi) and
|
|
sup.getEnclosingCallable() = c and
|
|
constructorStep(p.getAnAccess(), sup)
|
|
)
|
|
or
|
|
// a custom InputStream that wraps a tainted data source is tainted
|
|
inputStreamWrapper(sink.getConstructor(), argi)
|
|
or
|
|
sink.getConstructor().(TaintPreservingCallable).returnsTaintFrom(argToParam(sink, argi))
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Converts an argument index to a formal parameter index.
|
|
* This is relevant for varadic methods.
|
|
*/
|
|
private int argToParam(Call call, int argIdx) {
|
|
result = call.getArgument(argIdx).(Argument).getParameterPos()
|
|
}
|
|
|
|
/** Access to a method that passes taint from qualifier to argument. */
|
|
private predicate qualifierToArgumentStep(Expr tracked, Expr sink) {
|
|
exists(MethodAccess ma, int arg |
|
|
ma.getMethod().(TaintPreservingCallable).transfersTaint(-1, argToParam(ma, arg)) and
|
|
tracked = ma.getQualifier() and
|
|
sink = ma.getArgument(arg)
|
|
)
|
|
}
|
|
|
|
/** Access to a method that passes taint from the qualifier. */
|
|
private predicate qualifierToMethodStep(Expr tracked, MethodAccess sink) {
|
|
taintPreservingQualifierToMethod(sink.getMethod()) and
|
|
tracked = sink.getQualifier()
|
|
}
|
|
|
|
/**
|
|
* Methods that return tainted data when called on tainted data.
|
|
*/
|
|
private predicate taintPreservingQualifierToMethod(Method m) {
|
|
m instanceof CloneMethod
|
|
or
|
|
m.getDeclaringType().getQualifiedName().matches("%StringWriter") and
|
|
(
|
|
m.getName() = "getBuffer"
|
|
or
|
|
m.getName() = "toString"
|
|
)
|
|
or
|
|
m.getDeclaringType() instanceof TypeObjectInputStream and
|
|
m.getName().matches("read%")
|
|
or
|
|
m instanceof GetterMethod and
|
|
m.getDeclaringType().getADescendant() instanceof SpringUntrustedDataType and
|
|
not m.getDeclaringType() instanceof TypeObject
|
|
or
|
|
m.(TaintPreservingCallable).returnsTaintFrom(-1)
|
|
or
|
|
exists(JaxRsResourceMethod resourceMethod |
|
|
m.(GetterMethod).getDeclaringType() = resourceMethod.getAParameter().getType()
|
|
)
|
|
}
|
|
|
|
/** Access to a method that passes taint from an argument. */
|
|
private predicate argToMethodStep(Expr tracked, MethodAccess sink) {
|
|
exists(Method m, int i |
|
|
m = sink.getMethod() and
|
|
taintPreservingArgumentToMethod(m, argToParam(sink, i)) and
|
|
tracked = sink.getArgument(i)
|
|
)
|
|
or
|
|
exists(Method springResponseEntityOfOk |
|
|
sink.getMethod() = springResponseEntityOfOk and
|
|
springResponseEntityOfOk.getDeclaringType() instanceof SpringResponseEntity and
|
|
springResponseEntityOfOk.hasName(["ok", "of"]) and
|
|
tracked = sink.getArgument(0) and
|
|
tracked.getType() instanceof TypeString
|
|
)
|
|
or
|
|
exists(Method springResponseEntityBody |
|
|
sink.getMethod() = springResponseEntityBody and
|
|
springResponseEntityBody.getDeclaringType() instanceof SpringResponseEntityBodyBuilder and
|
|
springResponseEntityBody.hasName("body") and
|
|
tracked = sink.getArgument(0) and
|
|
tracked.getType() instanceof TypeString
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `method` is a library method that returns tainted data if its
|
|
* `arg`th argument is tainted.
|
|
*/
|
|
private predicate taintPreservingArgumentToMethod(Method method, int arg) {
|
|
method.getDeclaringType().hasQualifiedName("org.apache.commons.codec.binary", "Base64") and
|
|
(
|
|
method.getName() = "decodeBase64" and arg = 0
|
|
or
|
|
method.getName().matches("encodeBase64%") and arg = 0
|
|
)
|
|
or
|
|
method.(TaintPreservingCallable).returnsTaintFrom(arg)
|
|
}
|
|
|
|
/**
|
|
* Holds if `tracked` and `sink` are arguments to a method that transfers taint
|
|
* between arguments.
|
|
*/
|
|
private predicate argToArgStep(Expr tracked, Expr sink) {
|
|
exists(MethodAccess ma, Method method, int input, int output |
|
|
method.(TaintPreservingCallable).transfersTaint(argToParam(ma, input), argToParam(ma, output)) and
|
|
ma.getMethod() = method and
|
|
ma.getArgument(input) = tracked and
|
|
ma.getArgument(output) = sink
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `tracked` is the argument of a method that transfers taint
|
|
* from the argument to the qualifier and `sink` is the qualifier.
|
|
*/
|
|
private predicate argToQualifierStep(Expr tracked, Expr sink) {
|
|
exists(Method m, int i, MethodAccess ma |
|
|
taintPreservingArgumentToQualifier(m, argToParam(ma, i)) and
|
|
ma.getMethod() = m and
|
|
tracked = ma.getArgument(i) and
|
|
sink = ma.getQualifier()
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `method` is a method that transfers taint from argument to qualifier and
|
|
* `arg` is the index of the argument.
|
|
*/
|
|
private predicate taintPreservingArgumentToQualifier(Method method, int arg) {
|
|
method.(TaintPreservingCallable).transfersTaint(arg, -1)
|
|
}
|
|
|
|
/** A comparison or equality test with a constant. */
|
|
private predicate comparisonStep(Expr tracked, Expr sink) {
|
|
exists(Expr other |
|
|
exists(BinaryExpr e | e instanceof ComparisonExpr or e instanceof EqualityTest |
|
|
e = sink and
|
|
e.hasOperands(tracked, other)
|
|
)
|
|
or
|
|
exists(MethodAccess m | m.getMethod() instanceof EqualsMethod |
|
|
m = sink and
|
|
(
|
|
m.getQualifier() = tracked and m.getArgument(0) = other
|
|
or
|
|
m.getQualifier() = other and m.getArgument(0) = tracked
|
|
)
|
|
)
|
|
|
|
|
other.isCompileTimeConstant() or other instanceof NullLiteral
|
|
)
|
|
}
|
|
|
|
/** Flow through data serialization. */
|
|
private predicate serializationStep(Expr tracked, Expr sink) {
|
|
exists(ObjectOutputStreamVar v, VariableAssign def |
|
|
def = v.getADef() and
|
|
exists(MethodAccess ma, RValue use |
|
|
ma.getArgument(0) = tracked and
|
|
ma = v.getAWriteObjectMethodAccess() and
|
|
use = ma.getQualifier() and
|
|
defUsePair(def, use)
|
|
) and
|
|
exists(RValue outputstream, ClassInstanceExpr cie |
|
|
cie = def.getSource() and
|
|
outputstream = cie.getArgument(0) and
|
|
adjacentUseUse(outputstream, sink)
|
|
)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* A local variable that is assigned an `ObjectOutputStream`.
|
|
* Writing tainted data to such a stream causes the underlying
|
|
* `OutputStream` to be tainted.
|
|
*/
|
|
class ObjectOutputStreamVar extends LocalVariableDecl {
|
|
ObjectOutputStreamVar() {
|
|
exists(ClassInstanceExpr cie | cie = this.getAnAssignedValue() |
|
|
cie.getType() instanceof TypeObjectOutputStream
|
|
)
|
|
}
|
|
|
|
VariableAssign getADef() {
|
|
result.getSource().(ClassInstanceExpr).getType() instanceof TypeObjectOutputStream and
|
|
result.getDestVar() = this
|
|
}
|
|
|
|
MethodAccess getAWriteObjectMethodAccess() {
|
|
result.getQualifier() = this.getAnAccess() and
|
|
result.getMethod().hasName("writeObject")
|
|
}
|
|
}
|
|
|
|
/** Flow through string formatting. */
|
|
private predicate formatStep(Expr tracked, Expr sink) {
|
|
exists(FormatterVar v, VariableAssign def |
|
|
def = v.getADef() and
|
|
exists(MethodAccess ma, RValue use |
|
|
ma.getAnArgument() = tracked and
|
|
ma = v.getAFormatMethodAccess() and
|
|
use = ma.getQualifier() and
|
|
defUsePair(def, use)
|
|
) and
|
|
exists(RValue output, ClassInstanceExpr cie |
|
|
cie = def.getSource() and
|
|
output = cie.getArgument(0) and
|
|
adjacentUseUse(output, sink) and
|
|
exists(RefType t | output.getType().(RefType).getASourceSupertype*() = t |
|
|
t.hasQualifiedName("java.io", "OutputStream") or
|
|
t.hasQualifiedName("java.lang", "Appendable")
|
|
)
|
|
)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* A local variable that is assigned a `Formatter`.
|
|
* Writing tainted data to such a formatter causes the underlying
|
|
* `OutputStream` or `Appendable` to be tainted.
|
|
*/
|
|
private class FormatterVar extends LocalVariableDecl {
|
|
FormatterVar() {
|
|
exists(ClassInstanceExpr cie | cie = this.getAnAssignedValue() |
|
|
cie.getType() instanceof TypeFormatter
|
|
)
|
|
}
|
|
|
|
VariableAssign getADef() {
|
|
result.getSource().(ClassInstanceExpr).getType() instanceof TypeFormatter and
|
|
result.getDestVar() = this
|
|
}
|
|
|
|
MethodAccess getAFormatMethodAccess() {
|
|
result.getQualifier() = this.getAnAccess() and
|
|
result.getMethod().hasName("format")
|
|
}
|
|
}
|
|
|
|
/** The class `java.util.Formatter`. */
|
|
private class TypeFormatter extends Class {
|
|
TypeFormatter() { this.hasQualifiedName("java.util", "Formatter") }
|
|
}
|
|
|
|
private class FormatterCallable extends TaintPreservingCallable {
|
|
FormatterCallable() {
|
|
this.getDeclaringType() instanceof TypeFormatter and
|
|
(
|
|
this.hasName(["format", "out", "toString"])
|
|
or
|
|
this.(Constructor)
|
|
.getParameterType(0)
|
|
.(RefType)
|
|
.getASourceSupertype*()
|
|
.hasQualifiedName("java.lang", "Appendable")
|
|
)
|
|
}
|
|
|
|
override predicate returnsTaintFrom(int arg) {
|
|
if this instanceof Constructor then arg = 0 else arg = [-1 .. this.getNumberOfParameters()]
|
|
}
|
|
|
|
override predicate transfersTaint(int src, int sink) {
|
|
this.hasName("format") and
|
|
sink = -1 and
|
|
src = [0 .. this.getNumberOfParameters()]
|
|
}
|
|
}
|
|
|
|
private import StringBuilderVarModule
|
|
|
|
module StringBuilderVarModule {
|
|
/**
|
|
* A local variable that is initialized to a `StringBuilder`
|
|
* or `StringBuffer`. Such variables are often used to
|
|
* build up a query using string concatenation.
|
|
*/
|
|
class StringBuilderVar extends LocalVariableDecl {
|
|
StringBuilderVar() { this.getType() instanceof StringBuildingType }
|
|
|
|
/**
|
|
* Gets a call that adds something to this string builder, from the argument at the given index.
|
|
*/
|
|
MethodAccess getAnInput(int arg) {
|
|
result.getQualifier() = this.getAChainedReference() and
|
|
(
|
|
result.getMethod().getName() = "append" and arg = 0
|
|
or
|
|
result.getMethod().getName() = "insert" and arg = 1
|
|
or
|
|
result.getMethod().getName() = "replace" and arg = 2
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Gets a call that appends something to this string builder.
|
|
*/
|
|
MethodAccess getAnAppend() {
|
|
result.getQualifier() = this.getAChainedReference() and
|
|
result.getMethod().getName() = "append"
|
|
}
|
|
|
|
MethodAccess getNextAppend(MethodAccess append) {
|
|
result = this.getAnAppend() and
|
|
append = this.getAnAppend() and
|
|
(
|
|
result.getQualifier() = append
|
|
or
|
|
not exists(MethodAccess chainAccess | chainAccess.getQualifier() = append) and
|
|
exists(RValue sbva1, RValue sbva2 |
|
|
adjacentUseUse(sbva1, sbva2) and
|
|
append.getQualifier() = this.getAChainedReference(sbva1) and
|
|
result.getQualifier() = sbva2
|
|
)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Gets a call that converts this string builder to a string.
|
|
*/
|
|
MethodAccess getToStringCall() {
|
|
result.getQualifier() = this.getAChainedReference() and
|
|
result.getMethod().getName() = "toString"
|
|
}
|
|
|
|
private Expr getAChainedReference(VarAccess sbva) {
|
|
// All the methods on `StringBuilder` that return the same type return
|
|
// the same object.
|
|
result = callReturningSameType+(sbva) and sbva = this.getAnAccess()
|
|
or
|
|
result = sbva and sbva = this.getAnAccess()
|
|
}
|
|
|
|
/**
|
|
* Gets an expression that refers to this `StringBuilder`, possibly after some chained calls.
|
|
*/
|
|
Expr getAChainedReference() { result = this.getAChainedReference(_) }
|
|
}
|
|
}
|
|
|
|
private MethodAccess callReturningSameType(Expr ref) {
|
|
ref = result.getQualifier() and
|
|
result.getMethod().getReturnType() = ref.getType()
|
|
}
|
|
|
|
private SrcRefType entrypointType() {
|
|
exists(RemoteFlowSource s, RefType t |
|
|
s instanceof DataFlow::ExplicitParameterNode and
|
|
t = pragma[only_bind_out](s).getType() and
|
|
not t instanceof TypeObject and
|
|
result = t.getADescendant().getSourceDeclaration()
|
|
)
|
|
or
|
|
result = entrypointType().getAField().getType().(RefType).getSourceDeclaration()
|
|
}
|
|
|
|
private predicate entrypointFieldStep(DataFlow::Node src, DataFlow::Node sink) {
|
|
src = DataFlow::getFieldQualifier(sink.asExpr().(FieldRead)) and
|
|
src.getType().(RefType).getSourceDeclaration() = entrypointType()
|
|
}
|