Merge branch 'main' into xslt-injection

This commit is contained in:
Grzegorz Golawski
2020-08-30 22:45:31 +02:00
3527 changed files with 207125 additions and 87247 deletions

View File

@@ -151,33 +151,6 @@ class Container extends @container, Top {
* This is the absolute path of the container.
*/
override string toString() { result = getAbsolutePath() }
/**
* DEPRECATED: use `getAbsolutePath()`, `getBaseName()` or `getStem()` instead.
*
* Gets the name of this container.
*/
deprecated string getName() { result = getAbsolutePath() }
/**
* DEPRECATED: use `getBaseName()` or `getStem()` instead.
*
* The short name of this container, excluding its path and (for files) extension.
*
* For folders, the short name includes the extension (if any), so the short name
* of the folder with absolute path `/home/user/.m2` is `.m2`.
*/
deprecated string getShortName() {
folders(this, _, result) or
files(this, _, result, _, _)
}
/**
* DEPRECATED: use `getAbsolutePath()` instead.
*
* Gets the full name of this container, including its path and extension (if any).
*/
deprecated string getFullName() { result = getAbsolutePath() }
}
/** A folder. */
@@ -198,13 +171,6 @@ class File extends Container, @file {
/** Gets the URL of this file. */
override string getURL() { result = "file://" + this.getAbsolutePath() + ":0:0:0:0" }
/**
* DEPRECATED: use `getAbsolutePath()`, `getBaseName()` or `getStem()` instead.
*
* Holds if this file has the specified `name`.
*/
deprecated predicate hasName(string name) { name = this.getAbsolutePath() }
}
/**

View File

@@ -90,16 +90,16 @@ class Top extends @top {
/** A location maps language elements to positions in source files. */
class Location extends @location {
/** Gets the line number where this location starts. */
/** Gets the 1-based line number (inclusive) where this location starts. */
int getStartLine() { locations_default(this, _, result, _, _, _) }
/** Gets the column number where this location starts. */
/** Gets the 1-based column number (inclusive) where this location starts. */
int getStartColumn() { locations_default(this, _, _, result, _, _) }
/** Gets the line number where this location ends. */
/** Gets the 1-based line number (inclusive) where this location ends. */
int getEndLine() { locations_default(this, _, _, _, result, _) }
/** Gets the column number where this location ends. */
/** Gets the 1-based column number (inclusive) where this location ends. */
int getEndColumn() { locations_default(this, _, _, _, _, result) }
/**

View File

@@ -1,3 +1,8 @@
/**
* Provides classes and predicates for reasoning about instances of
* `java.util.Collection` and their methods.
*/
import java
/**
@@ -77,6 +82,7 @@ class CollectionMutator extends CollectionMethod {
class CollectionMutation extends MethodAccess {
CollectionMutation() { this.getMethod() instanceof CollectionMutator }
/** Holds if the result of this call is not immediately discarded. */
predicate resultIsChecked() { not this.getParent() instanceof ExprStmt }
}

View File

@@ -68,21 +68,27 @@ newtype Completion =
*/
ThrowCompletion(ThrowableType tt)
/** A completion that is either a `NormalCompletion` or a `BooleanCompletion`. */
class NormalOrBooleanCompletion extends Completion {
NormalOrBooleanCompletion() {
this instanceof NormalCompletion or this instanceof BooleanCompletion
}
/** Gets a textual representation of this completion. */
string toString() { result = "completion" }
}
/** Gets the completion `ContinueCompletion(NoLabel())`. */
ContinueCompletion anonymousContinueCompletion() { result = ContinueCompletion(NoLabel()) }
/** Gets the completion `ContinueCompletion(JustLabel(l))`. */
ContinueCompletion labelledContinueCompletion(Label l) { result = ContinueCompletion(JustLabel(l)) }
/** Gets the completion `BreakCompletion(NoLabel())`. */
BreakCompletion anonymousBreakCompletion() { result = BreakCompletion(NoLabel()) }
/** Gets the completion `BreakCompletion(JustLabel(l))`. */
BreakCompletion labelledBreakCompletion(Label l) { result = BreakCompletion(JustLabel(l)) }
/** Gets the completion `booleanCompletion(value, value)`. */
/** Gets the completion `BooleanCompletion(value, value)`. */
Completion basicBooleanCompletion(boolean value) { result = BooleanCompletion(value, value) }

View File

@@ -113,6 +113,7 @@ class ControlFlowNode extends Top, @exprparent {
result = succ(this, NormalCompletion())
}
/** Gets the basic block that contains this node. */
BasicBlock getBasicBlock() { result.getANode() = this }
}
@@ -404,7 +405,7 @@ private module ControlFlowGraphImpl {
* Expressions and statements with CFG edges in post-order AST traversal.
*
* This includes most expressions, except those that initiate or propagate branching control
* flow (`LogicExpr`, `ConditionalExpr`), and parentheses, which aren't in the CFG.
* flow (`LogicExpr`, `ConditionalExpr`).
* Only a few statements are included; those with specific side-effects
* occurring after the evaluation of their children, that is, `Call`, `ReturnStmt`,
* and `ThrowStmt`. CFG nodes without child nodes in the CFG that may complete
@@ -428,9 +429,10 @@ private module ControlFlowGraphImpl {
or
this instanceof CastExpr
or
this instanceof InstanceOfExpr
this instanceof InstanceOfExpr and not this.(InstanceOfExpr).isPattern()
or
this instanceof LocalVariableDeclExpr
this instanceof LocalVariableDeclExpr and
not this = any(InstanceOfExpr ioe).getLocalVariableDeclExpr()
or
this instanceof RValue
or
@@ -572,12 +574,16 @@ private module ControlFlowGraphImpl {
or
result = first(n.(PostOrderNode).firstChild())
or
result = first(n.(InstanceOfExpr).getExpr())
or
result = first(n.(SynchronizedStmt).getExpr())
or
result = n and
n instanceof Stmt and
not n instanceof PostOrderNode and
not n instanceof SynchronizedStmt
or
result = n and n instanceof SwitchExpr
}
/**
@@ -704,6 +710,12 @@ private module ControlFlowGraphImpl {
last(condexpr.getTrueExpr(), last, completion)
)
or
exists(InstanceOfExpr ioe | ioe.isPattern() and ioe = n |
last = n and completion = basicBooleanCompletion(false)
or
last = ioe.getLocalVariableDeclExpr() and completion = basicBooleanCompletion(true)
)
or
// The last node of a node executed in post-order is the node itself.
n.(PostOrderNode).mayCompleteNormally() and last = n and completion = NormalCompletion()
or
@@ -913,6 +925,14 @@ private module ControlFlowGraphImpl {
result = first(e.getFalseExpr())
)
or
exists(InstanceOfExpr ioe | ioe.isPattern() |
last(ioe.getExpr(), n, completion) and completion = NormalCompletion() and result = ioe
or
n = ioe and
result = ioe.getLocalVariableDeclExpr() and
completion = basicBooleanCompletion(true)
)
or
// In other expressions control flows from left to right and ends in the node itself.
exists(PostOrderNode p, int i |
last(p.getChildNode(i), n, completion) and completion = NormalCompletion()

View File

@@ -60,6 +60,12 @@ class Expr extends ExprParent, @expr {
/** Gets the statement containing this expression, if any. */
Stmt getEnclosingStmt() { statementEnclosingExpr(this, result) }
/**
* Gets a statement that directly or transitively contains this expression, if any.
* This is equivalent to `this.getEnclosingStmt().getEnclosingStmt*()`.
*/
Stmt getAnEnclosingStmt() { result = this.getEnclosingStmt().getEnclosingStmt*() }
/** Gets a child of this expression. */
Expr getAChildExpr() { exprs(result, _, _, this, _) }
@@ -305,10 +311,6 @@ class CompileTimeConstantExpr extends Expr {
/**
* Gets the integer value of this expression, where possible.
*
* All computations are performed on QL 32-bit `int`s, so no
* truncation is performed in the case of overflow within `byte` or `short`:
* `((byte)127)+((byte)1)` evaluates to 128 rather than to -128.
*
* Note that this does not handle the following cases:
*
* - values of type `long`,
@@ -332,7 +334,10 @@ class CompileTimeConstantExpr extends Expr {
else
if cast.getType().hasName("short")
then result = (val + 32768).bitAnd(65535) - 32768
else result = val
else
if cast.getType().hasName("char")
then result = val.bitAnd(65535)
else result = val
)
or
result = this.(PlusExpr).getExpr().(CompileTimeConstantExpr).getIntValue()
@@ -413,7 +418,7 @@ class ArrayAccess extends Expr, @arrayaccess {
/**
* An array creation expression.
*
* For example, an expression such as `new String[3][2]` or
* For example, an expression such as `new String[2][3]` or
* `new String[][] { { "a", "b", "c" } , { "d", "e", "f" } }`.
*
* In both examples, `String` is the type name. In the first
@@ -844,6 +849,7 @@ class EqualityTest extends BinaryExpr {
this instanceof NEExpr
}
/** Gets a boolean indicating whether this is `==` (true) or `!=` (false). */
boolean polarity() {
result = true and this instanceof EQExpr
or
@@ -1050,6 +1056,18 @@ class MemberRefExpr extends FunctionalExpr, @memberref {
override string toString() { result = "...::..." }
}
/** A conditional expression or a `switch` expression. */
class ChooseExpr extends Expr {
ChooseExpr() { this instanceof ConditionalExpr or this instanceof SwitchExpr }
/** Gets a result expression of this `switch` or conditional expression. */
Expr getAResultExpr() {
result = this.(ConditionalExpr).getTrueExpr() or
result = this.(ConditionalExpr).getFalseExpr() or
result = this.(SwitchExpr).getAResult()
}
}
/**
* A conditional expression of the form `a ? b : c`, where `a` is the condition,
* `b` is the expression that is evaluated if the condition evaluates to `true`,
@@ -1225,7 +1243,7 @@ class VariableAssign extends VariableUpdate {
}
/**
* Gets the source of this assignment, if any.
* Gets the source (right-hand side) of this assignment, if any.
*
* An initialization in a `CatchClause` or `EnhancedForStmt` is implicit and
* does not have a source.
@@ -1326,10 +1344,7 @@ class VarAccess extends Expr, @varaccess {
*/
predicate isLValue() {
exists(Assignment a | a.getDest() = this) or
exists(PreIncExpr e | e.getExpr() = this) or
exists(PreDecExpr e | e.getExpr() = this) or
exists(PostIncExpr e | e.getExpr() = this) or
exists(PostDecExpr e | e.getExpr() = this)
exists(UnaryAssignExpr e | e.getExpr() = this)
}
/**

View File

@@ -194,7 +194,16 @@ class TypeVariable extends BoundedType, @typevariable {
* and the second wildcard has a lower bound of `Float`.
*/
class Wildcard extends BoundedType, @wildcard {
/** Holds if this wildcard has an upper bound. */
/**
* Holds if this wildcard is either unconstrained (i.e. `?`) or
* has a type bound.
*/
override predicate hasTypeBound() { BoundedType.super.hasTypeBound() }
/**
* Holds if this wildcard is either unconstrained (i.e. `?`) or
* has an upper bound.
*/
predicate hasUpperBound() { wildcards(this, _, 1) }
/** Holds if this wildcard has a lower bound. */

View File

@@ -79,7 +79,7 @@ abstract class JavadocElement extends @javadocElement, Top {
abstract string getText();
}
/** A Javadoc tag. */
/** A Javadoc block tag. This does not include inline tags. */
class JavadocTag extends JavadocElement, JavadocParent, @javadocTag {
/** Gets the name of this Javadoc tag. */
string getTagName() { javadocTag(this, result, _, _) }

View File

@@ -1,3 +1,8 @@
/**
* Provides classes and predicates for reasoning about instances of
* `java.util.Map` and their methods.
*/
import java
import Collections
@@ -47,6 +52,7 @@ class MapSizeMethod extends MapMethod {
class MapMutation extends MethodAccess {
MapMutation() { this.getMethod() instanceof MapMutator }
/** Holds if the result of this call is not immediately discarded. */
predicate resultIsChecked() { not this.getParent() instanceof ExprStmt }
}
@@ -72,7 +78,9 @@ class FreshMap extends ClassInstanceExpr {
class MapPutCall extends MethodAccess {
MapPutCall() { getCallee().(MapMethod).hasName("put") }
/** Gets the key argument of this call. */
Expr getKey() { result = getArgument(0) }
/** Gets the value argument of this call. */
Expr getValue() { result = getArgument(1) }
}

View File

@@ -361,18 +361,23 @@ class Method extends Callable, @method {
override MethodAccess getAReference() { result = Callable.super.getAReference() }
override predicate isPublic() {
Callable.super.isPublic() or
// JLS 9.4: Every method declaration in the body of an interface is implicitly public.
getDeclaringType() instanceof Interface or
Callable.super.isPublic()
or
// JLS 9.4: Every method declaration in the body of an interface without an
// access modifier is implicitly public.
getDeclaringType() instanceof Interface and
not this.isPrivate()
or
exists(FunctionalExpr func | func.asMethod() = this)
}
override predicate isAbstract() {
Callable.super.isAbstract()
or
// JLS 9.4: An interface method lacking a `default` modifier or a `static` modifier
// JLS 9.4: An interface method lacking a `private`, `default`, or `static` modifier
// is implicitly abstract.
this.getDeclaringType() instanceof Interface and
not this.isPrivate() and
not this.isDefault() and
not this.isStatic()
}

File diff suppressed because it is too large Load Diff

View File

@@ -7,12 +7,14 @@ import JDKAnnotations
import Serializability
import semmle.code.java.dataflow.DefUse
/** Holds if `f` is a field that may be read by reflection. */
predicate reflectivelyRead(Field f) {
f instanceof SerializableField or
f.getAnAnnotation() instanceof ReflectiveAccessAnnotation or
referencedInXmlFile(f)
}
/** Holds if `f` is a field that may be written by reflection. */
predicate reflectivelyWritten(Field f) {
f instanceof DeserializableField or
f.getAnAnnotation() instanceof ReflectiveAccessAnnotation or
@@ -360,6 +362,7 @@ class ReflectiveFieldAccess extends ClassMethodAccess {
this.getCallee().hasName("getDeclaredField")
}
/** Gets the field accessed by this call. */
Field inferAccessedField() {
(
if this.getCallee().hasName("getDeclaredField")

View File

@@ -117,7 +117,7 @@ class IfStmt extends ConditionalStmt, @ifstmt {
* Gets the statement that is executed whenever the condition
* of this branch statement evaluates to `true`.
*/
override Stmt getTrueSuccessor() { result = getThen() }
deprecated override Stmt getTrueSuccessor() { result = getThen() }
/** Gets the `else` branch of this `if` statement. */
Stmt getElse() { result.isNthChildOf(this, 2) }
@@ -168,7 +168,7 @@ class ForStmt extends ConditionalStmt, @forstmt {
* Gets the statement that is executed whenever the condition
* of this branch statement evaluates to true.
*/
override Stmt getTrueSuccessor() { result = getStmt() }
deprecated override Stmt getTrueSuccessor() { result = getStmt() }
/**
* Gets a variable that is used as an iteration variable: it is defined,
@@ -228,7 +228,7 @@ class WhileStmt extends ConditionalStmt, @whilestmt {
* Gets the statement that is executed whenever the condition
* of this branch statement evaluates to true.
*/
override Stmt getTrueSuccessor() { result = getStmt() }
deprecated override Stmt getTrueSuccessor() { result = getStmt() }
/** Gets a printable representation of this statement. May include more detail than `toString()`. */
override string pp() { result = "while (...) " + this.getStmt().pp() }
@@ -249,7 +249,7 @@ class DoStmt extends ConditionalStmt, @dostmt {
* Gets the statement that is executed whenever the condition
* of this branch statement evaluates to `true`.
*/
override Stmt getTrueSuccessor() { result = getStmt() }
deprecated override Stmt getTrueSuccessor() { result = getStmt() }
/** Gets a printable representation of this statement. May include more detail than `toString()`. */
override string pp() { result = "do " + this.getStmt().pp() + " while (...)" }
@@ -522,8 +522,8 @@ class ThrowStmt extends Stmt, @throwstmt {
/**
* Gets the `catch` clause that catches the exception
* thrown by this `throws` statement and occurs
* in the same method as this `throws` statement,
* thrown by this `throw` statement and occurs
* in the same method as this `throw` statement,
* provided such a `catch` exists.
*/
CatchClause getLexicalCatchIfAny() {

View File

@@ -80,6 +80,7 @@ private newtype TFmtSyntax =
/** A syntax for format strings. */
class FmtSyntax extends TFmtSyntax {
/** Gets a textual representation of this format string syntax. */
string toString() {
result = "printf (%) syntax" and this = TFmtPrintf()
or
@@ -130,6 +131,7 @@ class FormattingCall extends Call {
formatWrapper(this.getCallee(), result, _)
}
/** Gets the format string syntax used by this call. */
FmtSyntax getSyntax() {
this.getCallee() instanceof StringFormatMethod and result = TFmtPrintf()
or
@@ -146,6 +148,7 @@ class FormattingCall extends Call {
)
}
/** Holds if this uses the "logger ({})" format syntax and the last argument is a `Throwable`. */
predicate hasTrailingThrowableArgument() {
getSyntax() = TFmtLogger() and
getLastArg().getType().(RefType).getASourceSupertype*() instanceof TypeThrowable
@@ -245,8 +248,7 @@ private predicate formatStringFragment(Expr fmt) {
e.(VarAccess).getVariable().getAnAssignedValue() = fmt or
e.(AddExpr).getLeftOperand() = fmt or
e.(AddExpr).getRightOperand() = fmt or
e.(ConditionalExpr).getTrueExpr() = fmt or
e.(ConditionalExpr).getFalseExpr() = fmt
e.(ChooseExpr).getAResultExpr() = fmt
)
}
@@ -290,9 +292,7 @@ private predicate formatStringValue(Expr e, string fmtvalue) {
fmtvalue = left + right
)
or
formatStringValue(e.(ConditionalExpr).getTrueExpr(), fmtvalue)
or
formatStringValue(e.(ConditionalExpr).getFalseExpr(), fmtvalue)
formatStringValue(e.(ChooseExpr).getAResultExpr(), fmtvalue)
or
exists(Method getprop, MethodAccess ma, string prop |
e = ma and

View File

@@ -637,10 +637,12 @@ class IntersectionType extends RefType, @class {
private RefType superInterface() { implInterface(this, result) }
/** Gets a textual representation of this type that includes all the intersected types. */
string getLongName() {
result = superType().toString() + concat(" & " + superInterface().toString())
}
/** Gets the first bound of this intersection type. */
RefType getFirstBound() { extendsReftype(this, result) }
}

View File

@@ -1,3 +1,8 @@
/**
* Provides classes and predicates for reasoning about guards and the control
* flow elements controlled by those guards.
*/
import java
private import semmle.code.java.controlflow.Dominance
private import semmle.code.java.controlflow.internal.GuardsLogic

View File

@@ -213,7 +213,7 @@ private predicate hasPossibleUnknownValue(SsaVariable v) {
/**
* Gets a sub-expression of `e` whose value can flow to `e` through
* `ConditionalExpr`s. Parentheses are also removed.
* `ConditionalExpr`s.
*/
private Expr possibleValue(Expr e) {
result = possibleValue(e.(ConditionalExpr).getTrueExpr())

View File

@@ -1,3 +1,7 @@
/**
* Provides classes for representing abstract bounds for use in, for example, range analysis.
*/
import java
private import SSA
private import RangeUtils
@@ -14,6 +18,7 @@ private newtype TBound =
* A bound that may be inferred for an expression plus/minus an integer delta.
*/
abstract class Bound extends TBound {
/** Gets a textual representation of this bound. */
abstract string toString();
/** Gets an expression that equals this bound plus `delta`. */
@@ -22,6 +27,13 @@ abstract class Bound extends TBound {
/** Gets an expression that equals this bound. */
Expr getExpr() { result = getExpr(0) }
/**
* Holds if this element is at the specified location.
* The location spans column `sc` of line `sl` to
* column `ec` of line `el` in file `path`.
* For more information, see
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
*/
predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
path = "" and sl = 0 and sc = 0 and el = 0 and ec = 0
}

View File

@@ -16,7 +16,9 @@ import semmle.code.java.frameworks.android.XmlParsing
import semmle.code.java.frameworks.android.WebView
import semmle.code.java.frameworks.JaxWS
import semmle.code.java.frameworks.android.Intent
import semmle.code.java.frameworks.SpringWeb
import semmle.code.java.frameworks.spring.SpringWeb
import semmle.code.java.frameworks.spring.SpringController
import semmle.code.java.frameworks.spring.SpringWebClient
import semmle.code.java.frameworks.Guice
import semmle.code.java.frameworks.struts.StrutsActions
import semmle.code.java.frameworks.Thrift
@@ -103,9 +105,22 @@ private class MessageBodyReaderParameterSource extends RemoteFlowSource {
override string getSourceType() { result = "MessageBodyReader parameter" }
}
private class SpringMultipartFileSource extends RemoteFlowSource {
SpringMultipartFileSource() {
exists(MethodAccess ma, Method m |
ma = this.asExpr() and
m = ma.getMethod() and
m.getDeclaringType().hasQualifiedName("org.springframework.web.multipart", "MultipartFile") and
m.getName().matches("get%")
)
}
override string getSourceType() { result = "Spring MultipartFile getter" }
}
private class SpringServletInputParameterSource extends RemoteFlowSource {
SpringServletInputParameterSource() {
this.asParameter().getAnAnnotation() instanceof SpringServletInputAnnotation
this.asParameter() = any(SpringRequestMappingParameter srmp | srmp.isTaintedInput())
}
override string getSourceType() { result = "Spring servlet input parameter" }
@@ -152,9 +167,13 @@ deprecated class RemoteUserInput extends UserInput {
RemoteUserInput() { this instanceof RemoteFlowSource }
}
/** Input that may be controlled by a local user. */
/** A node with input that may be controlled by a local user. */
abstract class LocalUserInput extends UserInput { }
/**
* A node with input from the local environment, such as files, standard in,
* environment variables, and main method parameters.
*/
class EnvInput extends LocalUserInput {
EnvInput() {
// Parameters to a main method.
@@ -180,6 +199,7 @@ class EnvInput extends LocalUserInput {
}
}
/** A node with input from a database. */
class DatabaseInput extends LocalUserInput {
DatabaseInput() { this.asExpr().(MethodAccess).getMethod() instanceof ResultSetGetStringMethod }
}
@@ -197,6 +217,8 @@ private class RemoteTaintedMethod extends Method {
this instanceof HttpServletRequestGetRequestURIMethod or
this instanceof HttpServletRequestGetRequestURLMethod or
this instanceof HttpServletRequestGetRemoteUserMethod or
this instanceof SpringWebRequestGetMethod or
this instanceof SpringRestTemplateResponseEntityMethod or
this instanceof ServletRequestGetBodyMethod or
this instanceof CookieGetValueMethod or
this instanceof CookieGetNameMethod or
@@ -214,6 +236,22 @@ private class RemoteTaintedMethod extends Method {
}
}
private class SpringWebRequestGetMethod extends Method {
SpringWebRequestGetMethod() {
exists(SpringWebRequest swr | this = swr.getAMethod() |
this.hasName("getDescription") or
this.hasName("getHeader") or
this.hasName("getHeaderNames") or
this.hasName("getHeaderValues") or
this.hasName("getParameter") or
this.hasName("getParameterMap") or
this.hasName("getParameterNames") or
this.hasName("getParameterValues")
// TODO consider getRemoteUser
)
}
}
private class EnvTaintedMethod extends Method {
EnvTaintedMethod() {
this instanceof MethodSystemGetenv or
@@ -222,10 +260,12 @@ private class EnvTaintedMethod extends Method {
}
}
/** The type `java.net.InetAddress`. */
class TypeInetAddr extends RefType {
TypeInetAddr() { this.getQualifiedName() = "java.net.InetAddress" }
}
/** A reverse DNS method. */
class ReverseDNSMethod extends Method {
ReverseDNSMethod() {
this.getDeclaringType() instanceof TypeInetAddr and

View File

@@ -10,8 +10,7 @@ private import RangeAnalysis
/** Gets an expression that might have the value `i`. */
private Expr exprWithIntValue(int i) {
result.(ConstantIntegerExpr).getIntValue() = i or
result.(ConditionalExpr).getTrueExpr() = exprWithIntValue(i) or
result.(ConditionalExpr).getFalseExpr() = exprWithIntValue(i)
result.(ChooseExpr).getAResultExpr() = exprWithIntValue(i)
}
/**

View File

@@ -45,8 +45,7 @@ private import semmle.code.java.frameworks.Assertions
/** Gets an expression that may be `null`. */
Expr nullExpr() {
result instanceof NullLiteral or
result.(ConditionalExpr).getTrueExpr() = nullExpr() or
result.(ConditionalExpr).getFalseExpr() = nullExpr() or
result.(ChooseExpr).getAResultExpr() = nullExpr() or
result.(AssignExpr).getSource() = nullExpr() or
result.(CastExpr).getExpr() = nullExpr()
}
@@ -81,9 +80,7 @@ private predicate unboxed(Expr e) {
or
exists(UnaryExpr un | un.getExpr() = e)
or
exists(ConditionalExpr cond | cond.getType() instanceof PrimitiveType |
cond.getTrueExpr() = e or cond.getFalseExpr() = e
)
exists(ChooseExpr cond | cond.getType() instanceof PrimitiveType | cond.getAResultExpr() = e)
or
exists(ConditionNode cond | cond.getCondition() = e)
or
@@ -579,7 +576,7 @@ private predicate varMaybeNullInBlock_corrCond(
* - int: A means a specific integer value and B means any other value.
*/
newtype TrackVarKind =
private newtype TrackVarKind =
TrackVarKindNull() or
TrackVarKindBool() or
TrackVarKindEnum() or
@@ -701,7 +698,7 @@ private predicate isReset(
}
/** The abstract value of the tracked variable. */
newtype TrackedValue =
private newtype TrackedValue =
TrackedValueA() or
TrackedValueB() or
TrackedValueUnknown()

View File

@@ -264,14 +264,21 @@ private newtype TReason =
* without going through a bounding condition.
*/
abstract class Reason extends TReason {
/** Gets a textual representation of this reason. */
abstract string toString();
}
/**
* A reason for an inferred bound that indicates that the bound is inferred
* without going through a bounding condition.
*/
class NoReason extends Reason, TNoReason {
override string toString() { result = "NoReason" }
}
/** A reason for an inferred bound pointing to a condition. */
class CondReason extends Reason, TCondReason {
/** Gets the condition that is the reason for the bound. */
Guard getCond() { this = TCondReason(result) }
override string toString() { result = getCond().toString() }

View File

@@ -142,6 +142,7 @@ class SsaReadPosition extends TSsaReadPosition {
/** Holds if `v` is read at this position. */
abstract predicate hasReadOfVar(SsaVariable v);
/** Gets a textual representation of this SSA read position. */
abstract string toString();
}

View File

@@ -89,6 +89,7 @@ class SsaSourceVariable extends TSsaSourceVariable {
this = TQualifiedField(result, _, _)
}
/** Gets a textual representation of this `SsaSourceVariable`. */
string toString() {
exists(LocalScopeVariable v, Callable c | this = TLocalVar(c, v) |
if c = v.getCallable()
@@ -112,6 +113,7 @@ class SsaSourceVariable extends TSsaSourceVariable {
)
}
/** Gets the source location for this element. */
Location getLocation() {
exists(LocalScopeVariable v | this = TLocalVar(_, v) and result = v.getLocation())
or
@@ -935,8 +937,10 @@ class SsaVariable extends TSsaVariable {
this = TSsaUntracked(_, result)
}
/** Gets a textual representation of this SSA variable. */
string toString() { none() }
/** Gets the source location for this element. */
Location getLocation() { result = getCFGNode().getLocation() }
/** Gets the `BasicBlock` in which this SSA variable is defined. */
@@ -1113,7 +1117,7 @@ class SsaPhiNode extends SsaVariable, TSsaPhiNode {
}
}
library class RefTypeCastExpr extends CastExpr {
private class RefTypeCastExpr extends CastExpr {
RefTypeCastExpr() { this.getType() instanceof RefType }
}

View File

@@ -552,9 +552,7 @@ private Sign exprSign(Expr e) {
result = s1.urshift(s2)
)
or
result = exprSign(e.(ConditionalExpr).getTrueExpr())
or
result = exprSign(e.(ConditionalExpr).getFalseExpr())
result = exprSign(e.(ChooseExpr).getAResultExpr())
or
result = exprSign(e.(CastExpr).getExpr())
)

View File

@@ -72,9 +72,7 @@ private predicate privateParamArg(Parameter p, Argument arg) {
* necessarily functionally determined by `n2`.
*/
private predicate joinStep0(TypeFlowNode n1, TypeFlowNode n2) {
n2.asExpr().(ConditionalExpr).getTrueExpr() = n1.asExpr()
or
n2.asExpr().(ConditionalExpr).getFalseExpr() = n1.asExpr()
n2.asExpr().(ChooseExpr).getAResultExpr() = n1.asExpr()
or
exists(Field f, Expr e |
f = n2.asField() and
@@ -226,9 +224,8 @@ private predicate upcastCand(TypeFlowNode n, RefType t, RefType t1, RefType t2)
or
exists(Parameter p | privateParamArg(p, n.asExpr()) and t2 = p.getType().getErasure())
or
exists(ConditionalExpr cond |
cond.getTrueExpr() = n.asExpr() or cond.getFalseExpr() = n.asExpr()
|
exists(ChooseExpr cond |
cond.getAResultExpr() = n.asExpr() and
t2 = cond.getType().getErasure()
)
)
@@ -308,6 +305,21 @@ private predicate instanceOfGuarded(VarAccess va, RefType t) {
)
}
/**
* Holds if `aa` is an access to a value that is guarded by `instanceof t`.
*/
predicate arrayInstanceOfGuarded(ArrayAccess aa, RefType t) {
exists(InstanceOfExpr ioe, BaseSsaVariable v1, BaseSsaVariable v2, ArrayAccess aa1 |
ioe.getExpr() = aa1 and
t = ioe.getTypeName().getType() and
aa1.getArray() = v1.getAUse() and
aa1.getIndexExpr() = v2.getAUse() and
aa.getArray() = v1.getAUse() and
aa.getIndexExpr() = v2.getAUse() and
guardControls_v1(ioe, aa.getBasicBlock(), true)
)
}
/**
* Holds if `n` has type `t` and this information is discarded, such that `t`
* might be a better type bound for nodes where `n` flows to.
@@ -318,6 +330,7 @@ private predicate typeFlowBase(TypeFlowNode n, RefType t) {
upcastEnhancedForStmt(n.asSsa(), srctype) or
downcastSuccessor(n.asExpr(), srctype) or
instanceOfGuarded(n.asExpr(), srctype) or
arrayInstanceOfGuarded(n.asExpr(), srctype) or
n.asExpr().(FunctionalExpr).getConstructedType() = srctype
|
t = srctype.(BoundedType).getAnUltimateUpperBoundType()
@@ -365,12 +378,12 @@ private predicate typeFlow(TypeFlowNode n, RefType t) {
}
pragma[nomagic]
predicate erasedTypeBound(RefType t) {
private predicate erasedTypeBound(RefType t) {
exists(RefType t0 | typeFlow(_, t0) and t = t0.getErasure())
}
pragma[nomagic]
predicate typeBound(RefType t) { typeFlow(_, t) }
private predicate typeBound(RefType t) { typeFlow(_, t) }
/**
* Holds if we have a bound for `n` that is better than `t`, taking only erased

View File

@@ -89,45 +89,96 @@ class ContainerType extends RefType {
}
private predicate taintPreservingQualifierToMethod(Method m) {
// java.util.Map.Entry
m.getDeclaringType() instanceof EntryType and
m.hasName("getValue")
m.hasName(["getValue", "setValue"])
or
// java.util.Iterable
m.getDeclaringType() instanceof IterableType and
m.hasName("iterator")
m.hasName(["iterator", "spliterator"])
or
// java.util.Iterator
m.getDeclaringType() instanceof IteratorType and
m.hasName("next")
or
// java.util.ListIterator
m.getDeclaringType() instanceof IteratorType and
m.hasName("previous")
or
// java.util.Enumeration
m.getDeclaringType() instanceof EnumerationType and
m.hasName("nextElement")
m.hasName(["asIterator", "nextElement"])
or
m.(MapMethod).hasName("entrySet")
// java.util.Map
m
.(MapMethod)
.hasName(["computeIfAbsent", "entrySet", "get", "getOrDefault", "put", "putIfAbsent",
"remove", "replace", "values"])
or
m.(MapMethod).hasName("get")
// java.util.Collection
m.(CollectionMethod).hasName(["parallelStream", "stream", "toArray"])
or
m.(MapMethod).hasName("remove")
or
m.(MapMethod).hasName("values")
or
m.(CollectionMethod).hasName("toArray")
or
m.(CollectionMethod).hasName("get")
// java.util.List
m.(CollectionMethod).hasName(["get", "listIterator", "set", "subList"])
or
m.(CollectionMethod).hasName("remove") and m.getParameterType(0).(PrimitiveType).hasName("int")
or
// java.util.Vector
m.(CollectionMethod).hasName(["elementAt", "elements", "firstElement", "lastElement"])
or
// java.util.Stack
m.(CollectionMethod).hasName(["peek", "pop"])
or
// java.util.Queue
// covered by Stack: peek()
m.(CollectionMethod).hasName(["element", "poll"])
or
m.(CollectionMethod).hasName("remove") and m.getNumberOfParameters() = 0
or
m.(CollectionMethod).hasName("subList")
// java.util.Deque
m
.(CollectionMethod)
.hasName(["getFirst", "getLast", "peekFirst", "peekLast", "pollFirst", "pollLast",
"removeFirst", "removeLast"])
or
m.(CollectionMethod).hasName("firstElement")
// java.util.concurrent.BlockingQueue
// covered by Queue: poll(long, TimeUnit)
m.(CollectionMethod).hasName("take")
or
m.(CollectionMethod).hasName("lastElement")
// java.util.concurrent.BlockingDeque
// covered by Deque: pollFirst(long, TimeUnit), pollLast(long, TimeUnit)
m.(CollectionMethod).hasName(["takeFirst", "takeLast"])
or
m.(CollectionMethod).hasName("poll")
// java.util.SortedSet
m.(CollectionMethod).hasName(["first", "headSet", "last", "subSet", "tailSet"])
or
m.(CollectionMethod).hasName("peek")
// java.util.NavigableSet
// covered by Deque: pollFirst(), pollLast()
// covered by SortedSet: headSet(E, boolean), subSet(E, boolean, E, boolean) and tailSet(E, boolean)
m
.(CollectionMethod)
.hasName(["ceiling", "descendingIterator", "descendingSet", "floor", "higher", "lower"])
or
m.(CollectionMethod).hasName("element")
// java.util.SortedMap
m.(MapMethod).hasName(["headMap", "subMap", "tailMap"])
or
// java.util.NavigableMap
// covered by SortedMap: headMap(K, boolean), subMap(K, boolean, K, boolean), tailMap(K, boolean)
m
.(MapMethod)
.hasName(["ceilingEntry", "descendingMap", "firstEntry", "floorEntry", "higherEntry",
"lastEntry", "lowerEntry", "pollFirstEntry", "pollLastEntry"])
or
// java.util.Dictionary
m
.getDeclaringType()
.getSourceDeclaration()
.getASourceSupertype*()
.hasQualifiedName("java.util", "Dictionary") and
m.hasName(["elements", "get", "put", "remove"])
or
// java.util.concurrent.ConcurrentHashMap
m.(MapMethod).hasName(["elements", "search", "searchEntries", "searchValues"])
}
private predicate qualifierToMethodStep(Expr tracked, MethodAccess sink) {
@@ -135,28 +186,162 @@ private predicate qualifierToMethodStep(Expr tracked, MethodAccess sink) {
tracked = sink.getQualifier()
}
private predicate qualifierToArgumentStep(Expr tracked, RValue sink) {
exists(MethodAccess ma |
ma.getMethod().(CollectionMethod).hasName("toArray") and
private predicate qualifierToArgumentStep(Expr tracked, Expr sink) {
exists(MethodAccess ma, CollectionMethod method |
method = ma.getMethod() and
(
// java.util.Vector
method.hasName("copyInto")
or
// java.util.concurrent.BlockingQueue
method.hasName("drainTo")
or
// java.util.Collection
method.hasName("toArray") and method.getParameter(0).getType() instanceof Array
) and
tracked = ma.getQualifier() and
sink = ma.getArgument(1)
sink = ma.getArgument(0)
)
}
private predicate taintPreservingArgumentToQualifier(Method method, int arg) {
method.(MapMethod).hasName("put") and arg = 1
// java.util.Map.Entry
method.getDeclaringType() instanceof EntryType and
method.hasName("setValue") and
arg = 0
or
// java.util.Map
method.(MapMethod).hasName(["merge", "put", "putIfAbsent"]) and arg = 1
or
method.(MapMethod).hasName("replace") and arg = method.getNumberOfParameters() - 1
or
method.(MapMethod).hasName("putAll") and arg = 0
or
method.(CollectionMethod).hasName("add") and arg = method.getNumberOfParameters() - 1
// java.util.ListIterator
method.getDeclaringType() instanceof IteratorType and
method.hasName(["add", "set"]) and
arg = 0
or
method.(CollectionMethod).hasName("addAll") and arg = method.getNumberOfParameters() - 1
or
method.(CollectionMethod).hasName("addElement") and arg = 0
// java.util.Collection
method.(CollectionMethod).hasName(["add", "addAll"]) and
// Refer to the last parameter to also cover List::add(int, E) and List::addAll(int, Collection)
arg = method.getNumberOfParameters() - 1
or
// java.util.List
// covered by Collection: add(int, E), addAll(int, Collection<? extends E>)
method.(CollectionMethod).hasName("set") and arg = 1
or
// java.util.Vector
method.(CollectionMethod).hasName(["addElement", "insertElementAt", "setElementAt"]) and arg = 0
or
// java.util.Stack
method.(CollectionMethod).hasName("push") and arg = 0
or
// java.util.Queue
method.(CollectionMethod).hasName("offer") and arg = 0
or
// java.util.Deque
// covered by Stack: push(E)
method.(CollectionMethod).hasName(["addFirst", "addLast", "offerFirst", "offerLast"]) and arg = 0
or
// java.util.concurrent.BlockingQueue
// covered by Queue: offer(E, long, TimeUnit)
method.(CollectionMethod).hasName("put") and arg = 0
or
// java.util.concurrent.TransferQueue
method.(CollectionMethod).hasName(["transfer", "tryTransfer"]) and arg = 0
or
// java.util.concurrent.BlockingDeque
// covered by Deque: offerFirst(E, long, TimeUnit), offerLast(E, long, TimeUnit)
method.(CollectionMethod).hasName(["putFirst", "putLast"]) and arg = 0
or
// java.util.Dictionary
method
.getDeclaringType()
.getSourceDeclaration()
.getASourceSupertype*()
.hasQualifiedName("java.util", "Dictionary") and
method.hasName("put") and
arg = 1
}
/**
* 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("java.util", "Collections") and
(
method
.hasName(["checkedCollection", "checkedList", "checkedMap", "checkedNavigableMap",
"checkedNavigableSet", "checkedSet", "checkedSortedMap", "checkedSortedSet",
"enumeration", "list", "max", "min", "singleton", "singletonList",
"synchronizedCollection", "synchronizedList", "synchronizedMap",
"synchronizedNavigableMap", "synchronizedNavigableSet", "synchronizedSet",
"synchronizedSortedMap", "synchronizedSortedSet", "unmodifiableCollection",
"unmodifiableList", "unmodifiableMap", "unmodifiableNavigableMap",
"unmodifiableNavigableSet", "unmodifiableSet", "unmodifiableSortedMap",
"unmodifiableSortedSet"]) and
arg = 0
or
method.hasName(["nCopies", "singletonMap"]) and arg = 1
)
or
method
.getDeclaringType()
.getSourceDeclaration()
.hasQualifiedName("java.util", ["List", "Map", "Set"]) and
method.hasName("copyOf") and
arg = 0
or
method.getDeclaringType().getSourceDeclaration().hasQualifiedName("java.util", "Map") and
(
method.hasName("of") and
arg = any(int i | i in [1 .. 10] | 2 * i - 1)
or
method.hasName("entry") and
arg = 1
)
or
method.getDeclaringType().hasQualifiedName("java.util", "Arrays") and
(
method.hasName(["copyOf", "copyOfRange", "spliterator", "stream"]) and
arg = 0
)
}
/**
* Holds if `method` is a library method that returns tainted data if any
* of its arguments are tainted.
*/
private predicate taintPreservingArgumentToMethod(Method method) {
method.getDeclaringType().getSourceDeclaration().hasQualifiedName("java.util", ["Set", "List"]) and
method.hasName("of")
or
method.getDeclaringType().getSourceDeclaration().hasQualifiedName("java.util", "Map") and
method.hasName("ofEntries")
}
/**
* Holds if `method` is a library method that writes tainted data to the
* `output`th argument if the `input`th argument is tainted.
*/
private predicate taintPreservingArgToArg(Method method, int input, int output) {
method.getDeclaringType().hasQualifiedName("java.util", "Collections") and
(
method.hasName(["copy", "fill"]) and
input = 1 and
output = 0
or
method.hasName("replaceAll") and input = 2 and output = 0
)
or
method.getDeclaringType().hasQualifiedName("java.util", "Arrays") and
(
method.hasName("fill") and
output = 0 and
input = method.getNumberOfParameters() - 1
)
}
private predicate argToQualifierStep(Expr tracked, Expr sink) {
@@ -168,13 +353,55 @@ private predicate argToQualifierStep(Expr tracked, Expr sink) {
)
}
/** Access to a method that passes taint from an argument. */
private predicate argToMethodStep(Expr tracked, MethodAccess sink) {
exists(Method m |
m = sink.getMethod() and
(
exists(int i |
taintPreservingArgumentToMethod(m, i) and
tracked = sink.getArgument(i)
)
or
m.getDeclaringType().hasQualifiedName("java.util", "Arrays") and
m.hasName("asList") and
tracked = sink.getAnArgument()
)
)
or
taintPreservingArgumentToMethod(sink.getMethod()) and
tracked = sink.getAnArgument()
}
/**
* 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 |
ma.getMethod() = method and
ma.getArgument(input) = tracked and
ma.getArgument(output) = sink and
(
taintPreservingArgToArg(method, input, output)
or
method.getDeclaringType().hasQualifiedName("java.util", "Collections") and
method.hasName("addAll") and
input >= 1 and
output = 0
)
)
}
/**
* Holds if the step from `n1` to `n2` is either extracting a value from a
* container, inserting a value into a container, or transforming one container
* to another. This is restricted to cases where `n2` is the returned value of
* a call.
*/
predicate containerReturnValueStep(Expr n1, Expr n2) { qualifierToMethodStep(n1, n2) }
predicate containerReturnValueStep(Expr n1, Expr n2) {
qualifierToMethodStep(n1, n2) or argToMethodStep(n1, n2)
}
/**
* Holds if the step from `n1` to `n2` is either extracting a value from a
@@ -183,7 +410,8 @@ predicate containerReturnValueStep(Expr n1, Expr n2) { qualifierToMethodStep(n1,
*/
predicate containerUpdateStep(Expr n1, Expr n2) {
qualifierToArgumentStep(n1, n2) or
argToQualifierStep(n1, n2)
argToQualifierStep(n1, n2) or
argToArgStep(n1, n2)
}
/**

View File

@@ -2,14 +2,13 @@ private import java
private import DataFlowPrivate
import semmle.code.java.dispatch.VirtualDispatch
cached
private module DispatchImpl {
/**
* Holds if the set of viable implementations that can be called by `ma`
* might be improved by knowing the call context. This is the case if the
* qualifier is the `i`th parameter of the enclosing callable `c`.
*/
private predicate benefitsFromCallContext(MethodAccess ma, Callable c, int i) {
private predicate mayBenefitFromCallContext(MethodAccess ma, Callable c, int i) {
exists(Parameter p |
2 <= strictcount(viableImpl(ma)) and
ma.getQualifier().(VarAccess).getVariable() = p and
@@ -28,7 +27,7 @@ private module DispatchImpl {
pragma[nomagic]
private predicate relevantContext(Call ctx, int i) {
exists(Callable c |
benefitsFromCallContext(_, c, i) and
mayBenefitFromCallContext(_, c, i) and
c = viableCallable(ctx)
)
}
@@ -53,14 +52,23 @@ private module DispatchImpl {
)
}
/**
* Holds if the set of viable implementations that can be called by `ma`
* might be improved by knowing the call context. This is the case if the
* qualifier is a parameter of the enclosing callable `c`.
*/
predicate mayBenefitFromCallContext(MethodAccess ma, Callable c) {
mayBenefitFromCallContext(ma, c, _)
}
/**
* Gets a viable dispatch target of `ma` in the context `ctx`. This is
* restricted to those `ma`s for which a context might make a difference.
*/
private Method viableImplInCallContext(MethodAccess ma, Call ctx) {
Method viableImplInCallContext(MethodAccess ma, Call ctx) {
result = viableImpl(ma) and
exists(int i, Callable c, Method def, RefType t, boolean exact |
benefitsFromCallContext(ma, c, i) and
mayBenefitFromCallContext(ma, c, i) and
c = viableCallable(ctx) and
contextArgHasType(ctx, i, t, exact) and
ma.getMethod() = def
@@ -136,57 +144,6 @@ private module DispatchImpl {
)
)
}
/**
* Holds if the call context `ctx` reduces the set of viable dispatch
* targets of `ma` in `c`.
*/
cached
predicate reducedViableImplInCallContext(MethodAccess ma, Callable c, Call ctx) {
exists(int tgts, int ctxtgts |
benefitsFromCallContext(ma, c, _) and
c = viableCallable(ctx) and
ctxtgts = count(viableImplInCallContext(ma, ctx)) and
tgts = strictcount(viableImpl(ma)) and
ctxtgts < tgts
)
}
/**
* Gets a viable dispatch target of `ma` in the context `ctx`. This is
* restricted to those `ma`s for which the context makes a difference.
*/
cached
Method prunedViableImplInCallContext(MethodAccess ma, Call ctx) {
result = viableImplInCallContext(ma, ctx) and
reducedViableImplInCallContext(ma, _, ctx)
}
/**
* Holds if flow returning from `m` to `ma` might return further and if
* this path restricts the set of call sites that can be returned to.
*/
cached
predicate reducedViableImplInReturn(Method m, MethodAccess ma) {
exists(int tgts, int ctxtgts |
benefitsFromCallContext(ma, _, _) and
m = viableImpl(ma) and
ctxtgts = count(Call ctx | m = viableImplInCallContext(ma, ctx)) and
tgts = strictcount(Call ctx | viableCallable(ctx) = ma.getEnclosingCallable()) and
ctxtgts < tgts
)
}
/**
* Gets a viable dispatch target of `ma` in the context `ctx`. This is
* restricted to those `ma`s and results for which the return flow from the
* result to `ma` restricts the possible context `ctx`.
*/
cached
Method prunedViableImplInCallContextReverse(MethodAccess ma, Call ctx) {
result = viableImplInCallContext(ma, ctx) and
reducedViableImplInReturn(result, ma)
}
}
import DispatchImpl

File diff suppressed because it is too large Load Diff

View File

@@ -22,17 +22,34 @@ private module Cached {
exists(int i |
viableParam(call, i, p) and
arg.argumentOf(call, i) and
compatibleTypes(getErasedNodeTypeBound(arg), getErasedNodeTypeBound(p))
compatibleTypes(getNodeType(arg), getNodeType(p))
)
}
pragma[nomagic]
private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKindExt kind) {
viableCallable(call) = result.getCallable() and
kind = result.getKind()
}
/**
* Holds if a value at return position `pos` can be returned to `out` via `call`,
* taking virtual dispatch into account.
*/
cached
predicate viableReturnPosOut(DataFlowCall call, ReturnPosition pos, Node out) {
exists(ReturnKindExt kind |
pos = viableReturnPos(call, kind) and
out = kind.getAnOutNode(call)
)
}
/** Provides predicates for calculating flow-through summaries. */
cached
private module FlowThrough {
/**
* The first flow-through approximation:
*
* - Input/output access paths are abstracted with a Boolean parameter
* - Input access paths are abstracted with a Boolean parameter
* that indicates (non-)emptiness.
*/
private module Cand {
@@ -40,83 +57,47 @@ private module Cached {
* Holds if `p` can flow to `node` in the same callable using only
* value-preserving steps.
*
* `read` indicates whether it is contents of `p` that can flow to `node`,
* and `stored` indicates whether it flows to contents of `node`.
* `read` indicates whether it is contents of `p` that can flow to `node`.
*/
pragma[nomagic]
private predicate parameterValueFlowCand(
ParameterNode p, Node node, boolean read, boolean stored
) {
private predicate parameterValueFlowCand(ParameterNode p, Node node, boolean read) {
p = node and
read = false and
stored = false
read = false
or
// local flow
exists(Node mid |
parameterValueFlowCand(p, mid, read, stored) and
parameterValueFlowCand(p, mid, read) and
simpleLocalFlowStep(mid, node)
)
or
// read
exists(Node mid, boolean readMid, boolean storedMid |
parameterValueFlowCand(p, mid, readMid, storedMid) and
readStep(mid, _, node) and
stored = false
|
// value neither read nor stored prior to read
readMid = false and
storedMid = false and
read = true
or
// value (possibly read and then) stored prior to read (same content)
read = readMid and
storedMid = true
)
or
// store
exists(Node mid |
parameterValueFlowCand(p, mid, read, false) and
storeStep(mid, _, node) and
stored = true
parameterValueFlowCand(p, mid, false) and
readStep(mid, _, node) and
read = true
)
or
// flow through: no prior read or store
// flow through: no prior read
exists(ArgumentNode arg |
parameterValueFlowArgCand(p, arg, false, false) and
argumentValueFlowsThroughCand(arg, node, read, stored)
parameterValueFlowArgCand(p, arg, false) and
argumentValueFlowsThroughCand(arg, node, read)
)
or
// flow through: no read or store inside method
// flow through: no read inside method
exists(ArgumentNode arg |
parameterValueFlowArgCand(p, arg, read, stored) and
argumentValueFlowsThroughCand(arg, node, false, false)
)
or
// flow through: possible prior read and prior store with compatible
// flow-through method
exists(ArgumentNode arg, boolean mid |
parameterValueFlowArgCand(p, arg, read, mid) and
argumentValueFlowsThroughCand(arg, node, mid, stored)
parameterValueFlowArgCand(p, arg, read) and
argumentValueFlowsThroughCand(arg, node, false)
)
}
pragma[nomagic]
private predicate parameterValueFlowArgCand(
ParameterNode p, ArgumentNode arg, boolean read, boolean stored
) {
parameterValueFlowCand(p, arg, read, stored)
private predicate parameterValueFlowArgCand(ParameterNode p, ArgumentNode arg, boolean read) {
parameterValueFlowCand(p, arg, read)
}
pragma[nomagic]
predicate parameterValueFlowsToPreUpdateCand(ParameterNode p, PostUpdateNode n) {
parameterValueFlowCand(p, n.getPreUpdateNode(), false, false)
}
pragma[nomagic]
private predicate parameterValueFlowsToPostUpdateCand(
ParameterNode p, PostUpdateNode n, boolean read
) {
parameterValueFlowCand(p, n, read, true)
parameterValueFlowCand(p, n.getPreUpdateNode(), false)
}
/**
@@ -125,33 +106,21 @@ private module Cached {
* into account.
*
* `read` indicates whether it is contents of `p` that can flow to the return
* node, and `stored` indicates whether it flows to contents of the return
* node.
*/
predicate parameterValueFlowReturnCand(
ParameterNode p, ReturnKindExt kind, boolean read, boolean stored
) {
predicate parameterValueFlowReturnCand(ParameterNode p, ReturnKind kind, boolean read) {
exists(ReturnNode ret |
parameterValueFlowCand(p, ret, read, stored) and
kind = TValueReturn(ret.getKind())
)
or
exists(ParameterNode p2, int pos2, PostUpdateNode n |
parameterValueFlowsToPostUpdateCand(p, n, read) and
parameterValueFlowsToPreUpdateCand(p2, n) and
p2.isParameterOf(_, pos2) and
kind = TParamUpdate(pos2) and
p != p2 and
stored = true
parameterValueFlowCand(p, ret, read) and
kind = ret.getKind()
)
}
pragma[nomagic]
private predicate argumentValueFlowsThroughCand0(
DataFlowCall call, ArgumentNode arg, ReturnKindExt kind, boolean read, boolean stored
DataFlowCall call, ArgumentNode arg, ReturnKind kind, boolean read
) {
exists(ParameterNode param | viableParamArg(call, param, arg) |
parameterValueFlowReturnCand(param, kind, read, stored)
parameterValueFlowReturnCand(param, kind, read)
)
}
@@ -159,308 +128,257 @@ private module Cached {
* Holds if `arg` flows to `out` through a call using only value-preserving steps,
* not taking call contexts into account.
*
* `read` indicates whether it is contents of `arg` that can flow to `out`, and
* `stored` indicates whether it flows to contents of `out`.
* `read` indicates whether it is contents of `arg` that can flow to `out`.
*/
predicate argumentValueFlowsThroughCand(
ArgumentNode arg, Node out, boolean read, boolean stored
) {
exists(DataFlowCall call, ReturnKindExt kind |
argumentValueFlowsThroughCand0(call, arg, kind, read, stored) and
out = kind.getAnOutNode(call)
predicate argumentValueFlowsThroughCand(ArgumentNode arg, Node out, boolean read) {
exists(DataFlowCall call, ReturnKind kind |
argumentValueFlowsThroughCand0(call, arg, kind, read) and
out = getAnOutNode(call, kind)
)
}
predicate cand(ParameterNode p, Node n) {
parameterValueFlowCand(p, n, _, _) and
parameterValueFlowCand(p, n, _) and
(
parameterValueFlowReturnCand(p, _, _, _)
parameterValueFlowReturnCand(p, _, _)
or
parameterValueFlowsToPreUpdateCand(p, _)
)
}
}
private module LocalFlowBigStep {
private predicate localFlowEntry(Node n) {
Cand::cand(_, n) and
(
n instanceof ParameterNode or
n instanceof OutNode or
n instanceof PostUpdateNode or
readStep(_, _, n) or
n instanceof CastNode
)
}
private predicate localFlowExit(Node n) {
Cand::cand(_, n) and
(
n instanceof ArgumentNode
or
n instanceof ReturnNode
or
Cand::parameterValueFlowsToPreUpdateCand(_, n)
or
storeStep(n, _, _)
or
readStep(n, _, _)
or
n instanceof CastNode
or
n =
any(PostUpdateNode pun | Cand::parameterValueFlowsToPreUpdateCand(_, pun))
.getPreUpdateNode()
)
}
pragma[nomagic]
private predicate localFlowStepPlus(Node node1, Node node2) {
localFlowEntry(node1) and
simpleLocalFlowStep(node1, node2) and
node1 != node2
or
exists(Node mid |
localFlowStepPlus(node1, mid) and
simpleLocalFlowStep(mid, node2) and
not mid instanceof CastNode
)
}
pragma[nomagic]
predicate localFlowBigStep(Node node1, Node node2) {
localFlowStepPlus(node1, node2) and
localFlowExit(node2)
}
}
/**
* The final flow-through calculation:
*
* - Input/output access paths are abstracted with a `ContentOption` parameter
* that represents the head of the access path. `TContentNone()` means that
* the access path is unrestricted.
* - Calculated flow is either value-preserving (`read = TReadStepTypesNone()`)
* or summarized as a single read step with before and after types recorded
* in the `ReadStepTypesOption` parameter.
* - Types are checked using the `compatibleTypes()` relation.
*/
cached
private module Final {
/**
* Holds if `p` can flow to `node` in the same callable using only
* value-preserving steps, not taking call contexts into account.
* value-preserving steps and possibly a single read step, not taking
* call contexts into account.
*
* `contentIn` describes the content of `p` that can flow to `node`
* (if any), and `contentOut` describes the content of `node` that
* it flows to (if any).
* If a read step was taken, then `read` captures the `Content`, the
* container type, and the content type.
*/
private predicate parameterValueFlow(
ParameterNode p, Node node, ContentOption contentIn, ContentOption contentOut
) {
parameterValueFlow0(p, node, contentIn, contentOut) and
predicate parameterValueFlow(ParameterNode p, Node node, ReadStepTypesOption read) {
parameterValueFlow0(p, node, read) and
if node instanceof CastingNode
then
// normal flow through
contentIn = TContentNone() and
contentOut = TContentNone() and
compatibleTypes(getErasedNodeTypeBound(p), getErasedNodeTypeBound(node))
read = TReadStepTypesNone() and
compatibleTypes(getNodeType(p), getNodeType(node))
or
// getter
exists(Content fIn |
contentIn.getContent() = fIn and
contentOut = TContentNone() and
compatibleTypes(fIn.getType(), getErasedNodeTypeBound(node))
)
or
// (getter+)setter
exists(Content fOut |
contentOut.getContent() = fOut and
compatibleTypes(fOut.getContainerType(), getErasedNodeTypeBound(node))
)
compatibleTypes(read.getContentType(), getNodeType(node))
else any()
}
pragma[nomagic]
private predicate parameterValueFlow0(
ParameterNode p, Node node, ContentOption contentIn, ContentOption contentOut
) {
private predicate parameterValueFlow0(ParameterNode p, Node node, ReadStepTypesOption read) {
p = node and
Cand::cand(p, _) and
contentIn = TContentNone() and
contentOut = TContentNone()
read = TReadStepTypesNone()
or
// local flow
exists(Node mid |
parameterValueFlow(p, mid, contentIn, contentOut) and
LocalFlowBigStep::localFlowBigStep(mid, node)
parameterValueFlow(p, mid, read) and
simpleLocalFlowStep(mid, node)
)
or
// read
exists(Node mid, Content f, ContentOption contentInMid, ContentOption contentOutMid |
parameterValueFlow(p, mid, contentInMid, contentOutMid) and
readStep(mid, f, node)
|
// value neither read nor stored prior to read
contentInMid = TContentNone() and
contentOutMid = TContentNone() and
contentIn.getContent() = f and
contentOut = TContentNone() and
Cand::parameterValueFlowReturnCand(p, _, true, _) and
compatibleTypes(getErasedNodeTypeBound(p), f.getContainerType())
or
// value (possibly read and then) stored prior to read (same content)
contentIn = contentInMid and
contentOutMid.getContent() = f and
contentOut = TContentNone()
exists(Node mid |
parameterValueFlow(p, mid, TReadStepTypesNone()) and
readStepWithTypes(mid, read.getContainerType(), read.getContent(), node,
read.getContentType()) and
Cand::parameterValueFlowReturnCand(p, _, true) and
compatibleTypes(getNodeType(p), read.getContainerType())
)
or
// store
exists(Node mid, Content f |
parameterValueFlow(p, mid, contentIn, TContentNone()) and
storeStep(mid, f, node) and
contentOut.getContent() = f
|
contentIn = TContentNone() and
compatibleTypes(getErasedNodeTypeBound(p), f.getType())
or
compatibleTypes(contentIn.getContent().getType(), f.getType())
)
or
// flow through: no prior read or store
parameterValueFlow0_0(TReadStepTypesNone(), p, node, read)
}
pragma[nomagic]
private predicate parameterValueFlow0_0(
ReadStepTypesOption mustBeNone, ParameterNode p, Node node, ReadStepTypesOption read
) {
// flow through: no prior read
exists(ArgumentNode arg |
parameterValueFlowArg(p, arg, TContentNone(), TContentNone()) and
argumentValueFlowsThrough(_, arg, contentIn, contentOut, node)
parameterValueFlowArg(p, arg, mustBeNone) and
argumentValueFlowsThrough(arg, read, node)
)
or
// flow through: no read or store inside method
// flow through: no read inside method
exists(ArgumentNode arg |
parameterValueFlowArg(p, arg, contentIn, contentOut) and
argumentValueFlowsThrough(_, arg, TContentNone(), TContentNone(), node)
)
or
// flow through: possible prior read and prior store with compatible
// flow-through method
exists(ArgumentNode arg, ContentOption contentMid |
parameterValueFlowArg(p, arg, contentIn, contentMid) and
argumentValueFlowsThrough(_, arg, contentMid, contentOut, node)
parameterValueFlowArg(p, arg, read) and
argumentValueFlowsThrough(arg, mustBeNone, node)
)
}
pragma[nomagic]
private predicate parameterValueFlowArg(
ParameterNode p, ArgumentNode arg, ContentOption contentIn, ContentOption contentOut
ParameterNode p, ArgumentNode arg, ReadStepTypesOption read
) {
parameterValueFlow(p, arg, contentIn, contentOut) and
Cand::argumentValueFlowsThroughCand(arg, _, _, _)
parameterValueFlow(p, arg, read) and
Cand::argumentValueFlowsThroughCand(arg, _, _)
}
pragma[nomagic]
private predicate argumentValueFlowsThrough0(
DataFlowCall call, ArgumentNode arg, ReturnKindExt kind, ContentOption contentIn,
ContentOption contentOut
DataFlowCall call, ArgumentNode arg, ReturnKind kind, ReadStepTypesOption read
) {
exists(ParameterNode param | viableParamArg(call, param, arg) |
parameterValueFlowReturn(param, _, kind, contentIn, contentOut)
parameterValueFlowReturn(param, kind, read)
)
}
/**
* Holds if `arg` flows to `out` through `call` using only value-preserving steps,
* not taking call contexts into account.
* Holds if `arg` flows to `out` through a call using only
* value-preserving steps and possibly a single read step, not taking
* call contexts into account.
*
* `contentIn` describes the content of `arg` that can flow to `out` (if any), and
* `contentOut` describes the content of `out` that it flows to (if any).
* If a read step was taken, then `read` captures the `Content`, the
* container type, and the content type.
*/
cached
predicate argumentValueFlowsThrough(
DataFlowCall call, ArgumentNode arg, ContentOption contentIn, ContentOption contentOut,
Node out
) {
exists(ReturnKindExt kind |
argumentValueFlowsThrough0(call, arg, kind, contentIn, contentOut) and
out = kind.getAnOutNode(call)
pragma[nomagic]
predicate argumentValueFlowsThrough(ArgumentNode arg, ReadStepTypesOption read, Node out) {
exists(DataFlowCall call, ReturnKind kind |
argumentValueFlowsThrough0(call, arg, kind, read) and
out = getAnOutNode(call, kind)
|
// normal flow through
contentIn = TContentNone() and
contentOut = TContentNone() and
compatibleTypes(getErasedNodeTypeBound(arg), getErasedNodeTypeBound(out))
read = TReadStepTypesNone() and
compatibleTypes(getNodeType(arg), getNodeType(out))
or
// getter
exists(Content fIn |
contentIn.getContent() = fIn and
contentOut = TContentNone() and
compatibleTypes(getErasedNodeTypeBound(arg), fIn.getContainerType()) and
compatibleTypes(fIn.getType(), getErasedNodeTypeBound(out))
)
or
// setter
exists(Content fOut |
contentIn = TContentNone() and
contentOut.getContent() = fOut and
compatibleTypes(getErasedNodeTypeBound(arg), fOut.getType()) and
compatibleTypes(fOut.getContainerType(), getErasedNodeTypeBound(out))
)
or
// getter+setter
exists(Content fIn, Content fOut |
contentIn.getContent() = fIn and
contentOut.getContent() = fOut and
compatibleTypes(getErasedNodeTypeBound(arg), fIn.getContainerType()) and
compatibleTypes(fOut.getContainerType(), getErasedNodeTypeBound(out))
)
compatibleTypes(getNodeType(arg), read.getContainerType()) and
compatibleTypes(read.getContentType(), getNodeType(out))
)
}
/**
* Holds if `p` can flow to the pre-update node associated with post-update
* node `n`, in the same callable, using only value-preserving steps.
* Holds if `arg` flows to `out` through a call using only
* value-preserving steps and a single read step, not taking call
* contexts into account, thus representing a getter-step.
*/
cached
predicate parameterValueFlowsToPreUpdate(ParameterNode p, PostUpdateNode n) {
parameterValueFlow(p, n.getPreUpdateNode(), TContentNone(), TContentNone())
}
pragma[nomagic]
private predicate parameterValueFlowsToPostUpdate(
ParameterNode p, PostUpdateNode n, ContentOption contentIn, ContentOption contentOut
) {
parameterValueFlow(p, n, contentIn, contentOut) and
contentOut.hasContent()
predicate getterStep(ArgumentNode arg, Content c, Node out) {
argumentValueFlowsThrough(arg, TReadStepTypesSome(_, c, _), out)
}
/**
* Holds if `p` can flow to a return node of kind `kind` in the same
* callable using only value-preserving steps.
* callable using only value-preserving steps and possibly a single read
* step.
*
* `contentIn` describes the content of `p` that can flow to the return
* node (if any), and `contentOut` describes the content of the return
* node that it flows to (if any).
* If a read step was taken, then `read` captures the `Content`, the
* container type, and the content type.
*/
cached
predicate parameterValueFlowReturn(
ParameterNode p, Node ret, ReturnKindExt kind, ContentOption contentIn,
ContentOption contentOut
private predicate parameterValueFlowReturn(
ParameterNode p, ReturnKind kind, ReadStepTypesOption read
) {
ret =
any(ReturnNode n |
parameterValueFlow(p, n, contentIn, contentOut) and
kind = TValueReturn(n.getKind())
)
or
ret =
any(PostUpdateNode n |
exists(ParameterNode p2, int pos2 |
parameterValueFlowsToPostUpdate(p, n, contentIn, contentOut) and
parameterValueFlowsToPreUpdate(p2, n) and
p2.isParameterOf(_, pos2) and
kind = TParamUpdate(pos2) and
p != p2
)
)
exists(ReturnNode ret |
parameterValueFlow(p, ret, read) and
kind = ret.getKind()
)
}
}
import Final
}
import FlowThrough
cached
private module DispatchWithCallContext {
/**
* Holds if the call context `ctx` reduces the set of viable run-time
* dispatch targets of call `call` in `c`.
*/
cached
predicate reducedViableImplInCallContext(DataFlowCall call, DataFlowCallable c, DataFlowCall ctx) {
exists(int tgts, int ctxtgts |
mayBenefitFromCallContext(call, c) and
c = viableCallable(ctx) and
ctxtgts = count(viableImplInCallContext(call, ctx)) and
tgts = strictcount(viableCallable(call)) and
ctxtgts < tgts
)
}
/**
* Gets a viable run-time dispatch target for the call `call` in the
* context `ctx`. This is restricted to those calls for which a context
* makes a difference.
*/
cached
DataFlowCallable prunedViableImplInCallContext(DataFlowCall call, DataFlowCall ctx) {
result = viableImplInCallContext(call, ctx) and
reducedViableImplInCallContext(call, _, ctx)
}
/**
* Holds if flow returning from callable `c` to call `call` might return
* further and if this path restricts the set of call sites that can be
* returned to.
*/
cached
predicate reducedViableImplInReturn(DataFlowCallable c, DataFlowCall call) {
exists(int tgts, int ctxtgts |
mayBenefitFromCallContext(call, _) and
c = viableCallable(call) and
ctxtgts = count(DataFlowCall ctx | c = viableImplInCallContext(call, ctx)) and
tgts = strictcount(DataFlowCall ctx | viableCallable(ctx) = call.getEnclosingCallable()) and
ctxtgts < tgts
)
}
/**
* Gets a viable run-time dispatch target for the call `call` in the
* context `ctx`. This is restricted to those calls and results for which
* the return flow from the result to `call` restricts the possible context
* `ctx`.
*/
cached
DataFlowCallable prunedViableImplInCallContextReverse(DataFlowCall call, DataFlowCall ctx) {
result = viableImplInCallContext(call, ctx) and
reducedViableImplInReturn(result, call)
}
}
import DispatchWithCallContext
/**
* Holds if `p` can flow to the pre-update node associated with post-update
* node `n`, in the same callable, using only value-preserving steps.
*/
cached
predicate parameterValueFlowsToPreUpdate(ParameterNode p, PostUpdateNode n) {
parameterValueFlow(p, n.getPreUpdateNode(), TReadStepTypesNone())
}
private predicate store(
Node node1, Content c, Node node2, DataFlowType contentType, DataFlowType containerType
) {
storeStep(node1, c, node2) and
readStep(_, c, _) and
contentType = getNodeType(node1) and
containerType = getNodeType(node2)
or
exists(Node n1, Node n2 |
n1 = node1.(PostUpdateNode).getPreUpdateNode() and
n2 = node2.(PostUpdateNode).getPreUpdateNode()
|
argumentValueFlowsThrough(n2, TReadStepTypesSome(containerType, c, contentType), n1)
or
readStep(n2, c, n1) and
contentType = getNodeType(n1) and
containerType = getNodeType(n2)
)
}
/**
* Holds if data can flow from `node1` to `node2` via a direct assignment to
* `f`.
@@ -469,21 +387,10 @@ private module Cached {
* been stored into, in order to handle cases like `x.f1.f2 = y`.
*/
cached
predicate storeDirect(Node node1, Content f, Node node2) {
storeStep(node1, f, node2) and readStep(_, f, _)
or
exists(Node n1, Node n2 |
n1 = node1.(PostUpdateNode).getPreUpdateNode() and
n2 = node2.(PostUpdateNode).getPreUpdateNode()
|
argumentValueFlowsThrough(_, n2, TContentSome(f), TContentNone(), n1)
or
readStep(n2, f, n1)
)
predicate store(Node node1, TypedContent tc, Node node2, DataFlowType contentType) {
store(node1, tc.getContent(), node2, contentType, tc.getContainerType())
}
import FlowThrough
/**
* Holds if the call context `call` either improves virtual dispatch in
* `callable` or if it allows us to prune unreachable nodes in `callable`.
@@ -520,6 +427,24 @@ private module Cached {
newtype TReturnKindExt =
TValueReturn(ReturnKind kind) or
TParamUpdate(int pos) { exists(ParameterNode p | p.isParameterOf(_, pos)) }
cached
newtype TBooleanOption =
TBooleanNone() or
TBooleanSome(boolean b) { b = true or b = false }
cached
newtype TTypedContent = MkTypedContent(Content c, DataFlowType t) { store(_, c, _, _, t) }
cached
newtype TAccessPathFront =
TFrontNil(DataFlowType t) or
TFrontHead(TypedContent tc)
cached
newtype TAccessPathFrontOption =
TAccessPathFrontNone() or
TAccessPathFrontSome(AccessPathFront apf)
}
/**
@@ -529,26 +454,38 @@ class CastingNode extends Node {
CastingNode() {
this instanceof ParameterNode or
this instanceof CastNode or
this instanceof OutNode or
this.(PostUpdateNode).getPreUpdateNode() instanceof ArgumentNode
this instanceof OutNodeExt or
// For reads, `x.f`, we want to check that the tracked type after the read (which
// is obtained by popping the head of the access path stack) is compatible with
// the type of `x.f`.
readStep(_, _, this)
}
}
newtype TContentOption =
TContentNone() or
TContentSome(Content f)
private predicate readStepWithTypes(
Node n1, DataFlowType container, Content c, Node n2, DataFlowType content
) {
readStep(n1, c, n2) and
container = getNodeType(n1) and
content = getNodeType(n2)
}
class ContentOption extends TContentOption {
Content getContent() { this = TContentSome(result) }
predicate hasContent() { exists(this.getContent()) }
string toString() {
result = this.getContent().toString()
or
not this.hasContent() and
result = "<none>"
private newtype TReadStepTypesOption =
TReadStepTypesNone() or
TReadStepTypesSome(DataFlowType container, Content c, DataFlowType content) {
readStepWithTypes(_, container, c, _, content)
}
private class ReadStepTypesOption extends TReadStepTypesOption {
predicate isSome() { this instanceof TReadStepTypesSome }
DataFlowType getContainerType() { this = TReadStepTypesSome(result, _, _) }
Content getContent() { this = TReadStepTypesSome(_, result, _) }
DataFlowType getContentType() { this = TReadStepTypesSome(_, _, result) }
string toString() { if this.isSome() then result = "Some(..)" else result = "None()" }
}
/**
@@ -575,13 +512,19 @@ abstract class CallContext extends TCallContext {
abstract predicate relevantFor(DataFlowCallable callable);
}
class CallContextAny extends CallContext, TAnyCallContext {
abstract class CallContextNoCall extends CallContext { }
class CallContextAny extends CallContextNoCall, TAnyCallContext {
override string toString() { result = "CcAny" }
override predicate relevantFor(DataFlowCallable callable) { any() }
}
abstract class CallContextCall extends CallContext { }
abstract class CallContextCall extends CallContext {
/** Holds if this call context may be `call`. */
bindingset[call]
abstract predicate matchesCall(DataFlowCall call);
}
class CallContextSpecificCall extends CallContextCall, TSpecificCall {
override string toString() {
@@ -592,6 +535,8 @@ class CallContextSpecificCall extends CallContextCall, TSpecificCall {
recordDataFlowCallSite(getCall(), callable)
}
override predicate matchesCall(DataFlowCall call) { call = this.getCall() }
DataFlowCall getCall() { this = TSpecificCall(result) }
}
@@ -601,9 +546,11 @@ class CallContextSomeCall extends CallContextCall, TSomeCall {
override predicate relevantFor(DataFlowCallable callable) {
exists(ParameterNode p | p.getEnclosingCallable() = callable)
}
override predicate matchesCall(DataFlowCall call) { any() }
}
class CallContextReturn extends CallContext, TReturn {
class CallContextReturn extends CallContextNoCall, TReturn {
override string toString() {
exists(DataFlowCall call | this = TReturn(_, call) | result = "CcReturn(" + call + ")")
}
@@ -678,6 +625,18 @@ class ReturnNodeExt extends Node {
}
}
/**
* A node to which data can flow from a call. Either an ordinary out node
* or a post-update node associated with a call argument.
*/
class OutNodeExt extends Node {
OutNodeExt() {
this instanceof OutNode
or
this.(PostUpdateNode).getPreUpdateNode() instanceof ArgumentNode
}
}
/**
* An extended return kind. A return kind describes how data can be returned
* from a callable. This can either be through a returned value or an updated
@@ -688,7 +647,7 @@ abstract class ReturnKindExt extends TReturnKindExt {
abstract string toString();
/** Gets a node corresponding to data flow out of `call`. */
abstract Node getAnOutNode(DataFlowCall call);
abstract OutNodeExt getAnOutNode(DataFlowCall call);
}
class ValueReturnKind extends ReturnKindExt, TValueReturn {
@@ -700,7 +659,9 @@ class ValueReturnKind extends ReturnKindExt, TValueReturn {
override string toString() { result = kind.toString() }
override Node getAnOutNode(DataFlowCall call) { result = getAnOutNode(call, this.getKind()) }
override OutNodeExt getAnOutNode(DataFlowCall call) {
result = getAnOutNode(call, this.getKind())
}
}
class ParamUpdateReturnKind extends ReturnKindExt, TParamUpdate {
@@ -712,9 +673,9 @@ class ParamUpdateReturnKind extends ReturnKindExt, TParamUpdate {
override string toString() { result = "param update " + pos }
override PostUpdateNode getAnOutNode(DataFlowCall call) {
override OutNodeExt getAnOutNode(DataFlowCall call) {
exists(ArgumentNode arg |
result.getPreUpdateNode() = arg and
result.(PostUpdateNode).getPreUpdateNode() = arg and
arg.argumentOf(call, this.getPosition())
)
}
@@ -779,77 +740,83 @@ DataFlowCallable resolveCall(DataFlowCall call, CallContext cc) {
result = viableCallable(call) and cc instanceof CallContextReturn
}
newtype TSummary =
TSummaryVal() or
TSummaryTaint() or
TSummaryReadVal(Content f) or
TSummaryReadTaint(Content f) or
TSummaryTaintStore(Content f)
predicate read = readStep/3;
/**
* A summary of flow through a callable. This can either be value-preserving
* if no additional steps are used, taint-flow if at least one additional step
* is used, or any one of those combined with a store or a read. Summaries
* recorded at a return node are restricted to include at least one additional
* step, as the value-based summaries are calculated independent of the
* configuration.
*/
class Summary extends TSummary {
/** An optional Boolean value. */
class BooleanOption extends TBooleanOption {
string toString() {
result = "Val" and this = TSummaryVal()
this = TBooleanNone() and result = "<none>"
or
result = "Taint" and this = TSummaryTaint()
or
exists(Content f |
result = "ReadVal " + f.toString() and this = TSummaryReadVal(f)
or
result = "ReadTaint " + f.toString() and this = TSummaryReadTaint(f)
or
result = "TaintStore " + f.toString() and this = TSummaryTaintStore(f)
)
}
/** Gets the summary that results from extending this with an additional step. */
Summary additionalStep() {
this = TSummaryVal() and result = TSummaryTaint()
or
this = TSummaryTaint() and result = TSummaryTaint()
or
exists(Content f | this = TSummaryReadVal(f) and result = TSummaryReadTaint(f))
or
exists(Content f | this = TSummaryReadTaint(f) and result = TSummaryReadTaint(f))
}
/** Gets the summary that results from extending this with a read. */
Summary readStep(Content f) { this = TSummaryVal() and result = TSummaryReadVal(f) }
/** Gets the summary that results from extending this with a store. */
Summary storeStep(Content f) { this = TSummaryTaint() and result = TSummaryTaintStore(f) }
/** Gets the summary that results from extending this with `step`. */
bindingset[this, step]
Summary compose(Summary step) {
this = TSummaryVal() and result = step
or
this = TSummaryTaint() and
(step = TSummaryTaint() or step = TSummaryTaintStore(_)) and
result = step
or
exists(Content f |
this = TSummaryReadVal(f) and step = TSummaryTaint() and result = TSummaryReadTaint(f)
)
or
this = TSummaryReadTaint(_) and step = TSummaryTaint() and result = this
}
/** Holds if this summary does not include any taint steps. */
predicate isPartial() {
this = TSummaryVal() or
this = TSummaryReadVal(_)
this = TBooleanSome(any(boolean b | result = b.toString()))
}
}
pragma[noinline]
DataFlowType getErasedNodeTypeBound(Node n) { result = getErasedRepr(n.getTypeBound()) }
/** Content tagged with the type of a containing object. */
class TypedContent extends MkTypedContent {
private Content c;
private DataFlowType t;
predicate readDirect = readStep/3;
TypedContent() { this = MkTypedContent(c, t) }
/** Gets the content. */
Content getContent() { result = c }
/** Gets the container type. */
DataFlowType getContainerType() { result = t }
/** Gets a textual representation of this content. */
string toString() { result = c.toString() }
}
/**
* The front of an access path. This is either a head or a nil.
*/
abstract class AccessPathFront extends TAccessPathFront {
abstract string toString();
abstract DataFlowType getType();
abstract boolean toBoolNonEmpty();
predicate headUsesContent(TypedContent tc) { this = TFrontHead(tc) }
predicate isClearedAt(Node n) {
exists(TypedContent tc |
this.headUsesContent(tc) and
clearsContent(n, tc.getContent())
)
}
}
class AccessPathFrontNil extends AccessPathFront, TFrontNil {
private DataFlowType t;
AccessPathFrontNil() { this = TFrontNil(t) }
override string toString() { result = ppReprType(t) }
override DataFlowType getType() { result = t }
override boolean toBoolNonEmpty() { result = false }
}
class AccessPathFrontHead extends AccessPathFront, TFrontHead {
private TypedContent tc;
AccessPathFrontHead() { this = TFrontHead(tc) }
override string toString() { result = tc.toString() }
override DataFlowType getType() { result = tc.getContainerType() }
override boolean toBoolNonEmpty() { result = true }
}
/** An optional access path front. */
class AccessPathFrontOption extends TAccessPathFrontOption {
string toString() {
this = TAccessPathFrontNone() and result = "<none>"
or
this = TAccessPathFrontSome(any(AccessPathFront apf | result = apf.toString()))
}
}

View File

@@ -37,21 +37,12 @@ module Consistency {
)
}
query predicate uniqueTypeBound(Node n, string msg) {
query predicate uniqueType(Node n, string msg) {
exists(int c |
n instanceof RelevantNode and
c = count(n.getTypeBound()) and
c = count(getNodeType(n)) and
c != 1 and
msg = "Node should have one type bound but has " + c + "."
)
}
query predicate uniqueTypeRepr(Node n, string msg) {
exists(int c |
n instanceof RelevantNode and
c = count(getErasedRepr(n.getTypeBound())) and
c != 1 and
msg = "Node should have one type representation but has " + c + "."
msg = "Node should have one type but has " + c + "."
)
}
@@ -104,7 +95,7 @@ module Consistency {
msg = "Local flow step does not preserve enclosing callable."
}
private DataFlowType typeRepr() { result = getErasedRepr(any(Node n).getTypeBound()) }
private DataFlowType typeRepr() { result = getNodeType(_) }
query predicate compatibleTypesReflexive(DataFlowType t, string msg) {
t = typeRepr() and

View File

@@ -129,15 +129,6 @@ private predicate instanceFieldAssign(Expr src, FieldAccess fa) {
)
}
/**
* Gets an upper bound on the type of `f`.
*/
private Type getFieldTypeBound(Field f) {
fieldTypeFlow(f, result, _)
or
not fieldTypeFlow(f, _, _) and result = f.getType()
}
private newtype TContent =
TFieldContent(InstanceField f) or
TCollectionContent() or
@@ -154,12 +145,6 @@ class Content extends TContent {
predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
path = "" and sl = 0 and sc = 0 and el = 0 and ec = 0
}
/** Gets the erased type of the object containing this content. */
abstract DataFlowType getContainerType();
/** Gets the erased type of this content. */
abstract DataFlowType getType();
}
private class FieldContent extends Content, TFieldContent {
@@ -174,26 +159,14 @@ private class FieldContent extends Content, TFieldContent {
override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
f.getLocation().hasLocationInfo(path, sl, sc, el, ec)
}
override DataFlowType getContainerType() { result = getErasedRepr(f.getDeclaringType()) }
override DataFlowType getType() { result = getErasedRepr(getFieldTypeBound(f)) }
}
private class CollectionContent extends Content, TCollectionContent {
override string toString() { result = "collection" }
override DataFlowType getContainerType() { none() }
override DataFlowType getType() { none() }
}
private class ArrayContent extends Content, TArrayContent {
override string toString() { result = "array" }
override DataFlowType getContainerType() { none() }
override DataFlowType getType() { none() }
}
/**
@@ -222,12 +195,21 @@ predicate readStep(Node node1, Content f, Node node2) {
)
}
/**
* Holds if values stored inside content `c` are cleared at node `n`. For example,
* any value stored inside `f` is cleared at the pre-update node associated with `x`
* in `x.f = newValue`.
*/
predicate clearsContent(Node n, Content c) {
n = any(PostUpdateNode pun | storeStep(_, c, pun)).getPreUpdateNode()
}
/**
* Gets a representative (boxed) type for `t` for the purpose of pruning
* possible flow. A single type is used for all numeric types to account for
* numeric conversions, and otherwise the erasure is used.
*/
DataFlowType getErasedRepr(Type t) {
private DataFlowType getErasedRepr(Type t) {
exists(Type e | e = t.getErasure() |
if e instanceof NumericOrCharType
then result.(BoxedType).getPrimitiveType().getName() = "double"
@@ -240,6 +222,9 @@ DataFlowType getErasedRepr(Type t) {
t instanceof NullType and result instanceof TypeObject
}
pragma[noinline]
DataFlowType getNodeType(Node n) { result = getErasedRepr(n.getTypeBound()) }
/** Gets a string representation of a type returned by `getErasedRepr`. */
string ppReprType(Type t) {
if t.(BoxedType).getPrimitiveType().getName() = "double"
@@ -338,3 +323,6 @@ int accessPathLimit() { result = 5 }
predicate isImmutableOrUnobservable(Node n) {
n.getType() instanceof ImmutableType or n instanceof ImplicitVarargsArray
}
/** Holds if `n` should be hidden from path explanations. */
predicate nodeIsHidden(Node n) { none() }

View File

@@ -78,15 +78,19 @@ class Node extends TNode {
result = this.(ImplicitPostUpdateNode).getPreUpdateNode().getType()
}
/** Gets the callable in which this node occurs. */
Callable getEnclosingCallable() {
private Callable getEnclosingCallableImpl() {
result = this.asExpr().getEnclosingCallable() or
result = this.asParameter().getCallable() or
result = this.(ImplicitVarargsArray).getCall().getEnclosingCallable() or
result = this.(InstanceParameterNode).getCallable() or
result = this.(ImplicitInstanceAccess).getInstanceAccess().getEnclosingCallable() or
result = this.(MallocNode).getClassInstanceExpr().getEnclosingCallable() or
result = this.(ImplicitPostUpdateNode).getPreUpdateNode().getEnclosingCallable()
result = this.(ImplicitPostUpdateNode).getPreUpdateNode().getEnclosingCallableImpl()
}
/** Gets the callable in which this node occurs. */
Callable getEnclosingCallable() {
result = unique(DataFlowCallable c | c = this.getEnclosingCallableImpl() | c)
}
private Type getImprovedTypeBound() {
@@ -397,11 +401,34 @@ predicate simpleLocalFlowStep(Node node1, Node node2) {
or
node2.asExpr().(CastExpr).getExpr() = node1.asExpr()
or
node2.asExpr().(ConditionalExpr).getTrueExpr() = node1.asExpr()
or
node2.asExpr().(ConditionalExpr).getFalseExpr() = node1.asExpr()
node2.asExpr().(ChooseExpr).getAResultExpr() = node1.asExpr()
or
node2.asExpr().(AssignExpr).getSource() = node1.asExpr()
or
exists(MethodAccess ma, Method m |
ma = node2.asExpr() and
m = ma.getMethod() and
m.getDeclaringType().hasQualifiedName("java.util", "Objects") and
(
m.hasName(["requireNonNull", "requireNonNullElseGet"]) and node1.asExpr() = ma.getArgument(0)
or
m.hasName("requireNonNullElse") and node1.asExpr() = ma.getAnArgument()
or
m.hasName("toString") and node1.asExpr() = ma.getArgument(1)
)
)
or
exists(MethodAccess ma, Method m |
ma = node2.asExpr() and
m = ma.getMethod() and
m
.getDeclaringType()
.getSourceDeclaration()
.getASourceSupertype*()
.hasQualifiedName("java.util", "Stack") and
m.hasName("push") and
node1.asExpr() = ma.getArgument(0)
)
}
/**

View File

@@ -8,6 +8,8 @@ private import semmle.code.java.security.Validation
private import semmle.code.java.frameworks.android.Intent
private import semmle.code.java.frameworks.Guice
private import semmle.code.java.frameworks.Protobuf
private import semmle.code.java.frameworks.spring.SpringController
private import semmle.code.java.frameworks.spring.SpringHttp
private import semmle.code.java.Maps
private import semmle.code.java.dataflow.internal.ContainerFlow
private import semmle.code.java.frameworks.jackson.JacksonSerializability
@@ -252,6 +254,22 @@ private predicate constructorStep(Expr tracked, ConstructorCall sink) {
or
// a custom InputStream that wraps a tainted data source is tainted
inputStreamWrapper(sink.getConstructor(), argi)
or
// A SpringHttpEntity is a wrapper around a body and some headers
// Track flow through iff body is a String
exists(SpringHttpEntity she |
sink.getConstructor() = she.getAConstructor() and
argi = 0 and
tracked.getType() instanceof TypeString
)
or
// A SpringRequestEntity is a wrapper around a body and some headers
// Track flow through iff body is a String
exists(SpringResponseEntity sre |
sink.getConstructor() = sre.getAConstructor() and
argi = 0 and
tracked.getType() instanceof TypeString
)
)
}
@@ -292,10 +310,13 @@ private predicate qualifierToMethodStep(Expr tracked, MethodAccess sink) {
* Methods that return tainted data when called on tainted data.
*/
private predicate taintPreservingQualifierToMethod(Method m) {
m instanceof CloneMethod
or
m.getDeclaringType() instanceof TypeString and
(
m.getName() = "concat" or
m.getName() = "endsWith" or
m.getName() = "formatted" or
m.getName() = "getBytes" or
m.getName() = "split" or
m.getName() = "substring" or
@@ -321,7 +342,11 @@ private predicate taintPreservingQualifierToMethod(Method m) {
)
or
m.getDeclaringType().getQualifiedName().matches("%StringWriter") and
m.getName() = "toString"
(
m.getName() = "getBuffer"
or
m.getName() = "toString"
)
or
m.getDeclaringType().hasQualifiedName("java.util", "StringTokenizer") and
m.getName().matches("next%")
@@ -334,7 +359,8 @@ private predicate taintPreservingQualifierToMethod(Method m) {
or
(
m.getDeclaringType().hasQualifiedName("java.lang", "StringBuilder") or
m.getDeclaringType().hasQualifiedName("java.lang", "StringBuffer")
m.getDeclaringType().hasQualifiedName("java.lang", "StringBuffer") or
m.getDeclaringType().hasQualifiedName("java.io", "StringWriter")
) and
(m.getName() = "toString" or m.getName() = "append")
or
@@ -352,6 +378,21 @@ private predicate taintPreservingQualifierToMethod(Method m) {
m = any(GuiceProvider gp).getAnOverridingGetMethod()
or
m = any(ProtobufMessageLite p).getAGetterMethod()
or
m instanceof GetterMethod and m.getDeclaringType() instanceof SpringUntrustedDataType
or
m.getDeclaringType() instanceof SpringHttpEntity and
m.getName().regexpMatch("getBody|getHeaders")
or
exists(SpringHttpHeaders headers | m = headers.getAMethod() |
m.getReturnType() instanceof TypeString
or
exists(ParameterizedType stringlist |
m.getReturnType().(RefType).getASupertype*() = stringlist and
stringlist.getSourceDeclaration().hasQualifiedName("java.util", "List") and
stringlist.getTypeArgument(0) instanceof TypeString
)
)
}
private class StringReplaceMethod extends Method {
@@ -377,9 +418,9 @@ private predicate unsafeEscape(MethodAccess ma) {
/** Access to a method that passes taint from an argument. */
private predicate argToMethodStep(Expr tracked, MethodAccess sink) {
exists(Method m, int i |
m = sink.(MethodAccess).getMethod() and
m = sink.getMethod() and
taintPreservingArgumentToMethod(m, i) and
tracked = sink.(MethodAccess).getArgument(i)
tracked = sink.getArgument(i)
)
or
exists(MethodAccess ma |
@@ -387,6 +428,22 @@ private predicate argToMethodStep(Expr tracked, MethodAccess sink) {
tracked = ma.getAnArgument() and
sink = ma
)
or
exists(Method springResponseEntityOfOk |
sink.getMethod() = springResponseEntityOfOk and
springResponseEntityOfOk.getDeclaringType() instanceof SpringResponseEntity and
springResponseEntityOfOk.getName().regexpMatch("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.getName().regexpMatch("body") and
tracked = sink.getArgument(0) and
tracked.getType() instanceof TypeString
)
}
/**
@@ -395,7 +452,7 @@ private predicate argToMethodStep(Expr tracked, MethodAccess sink) {
*/
private predicate taintPreservingArgumentToMethod(Method method) {
method.getDeclaringType() instanceof TypeString and
(method.hasName("format") or method.hasName("join"))
(method.hasName("format") or method.hasName("formatted") or method.hasName("join"))
}
/**
@@ -434,7 +491,15 @@ private predicate taintPreservingArgumentToMethod(Method method, int arg) {
or
(
method.getDeclaringType().hasQualifiedName("java.util", "Base64$Encoder") or
method.getDeclaringType().hasQualifiedName("java.util", "Base64$Decoder")
method.getDeclaringType().hasQualifiedName("java.util", "Base64$Decoder") or
method
.getDeclaringType()
.getASupertype*()
.hasQualifiedName("org.apache.commons.codec", "Encoder") or
method
.getDeclaringType()
.getASupertype*()
.hasQualifiedName("org.apache.commons.codec", "Decoder")
) and
(
method.getName() = "encode" and arg = 0 and method.getNumberOfParameters() = 1
@@ -497,6 +562,10 @@ private predicate taintPreservingArgumentToMethod(Method method, int arg) {
method instanceof JacksonWriteValueMethod and
method.getNumberOfParameters() = 1 and
arg = 0
or
method.getDeclaringType().hasQualifiedName("java.io", "StringWriter") and
method.hasName("append") and
arg = 0
}
/**
@@ -571,9 +640,20 @@ private predicate argToQualifierStep(Expr tracked, Expr sink) {
private predicate taintPreservingArgumentToQualifier(Method method, int arg) {
exists(Method write |
method.overrides*(write) and
write.getDeclaringType().hasQualifiedName("java.io", "OutputStream") and
write.hasName("write") and
arg = 0
arg = 0 and
(
write.getDeclaringType().hasQualifiedName("java.io", "OutputStream")
or
write.getDeclaringType().hasQualifiedName("java.io", "StringWriter")
)
)
or
exists(Method append |
method.overrides*(append) and
append.hasName("append") and
arg = 0 and
append.getDeclaringType().hasQualifiedName("java.io", "StringWriter")
)
}

View File

@@ -26,7 +26,7 @@ private import TaintTrackingParameter::Private
* To create a configuration, extend this class with a subclass whose
* characteristic predicate is a unique singleton string. For example, write
*
* ```
* ```ql
* class MyAnalysisConfiguration extends TaintTracking::Configuration {
* MyAnalysisConfiguration() { this = "MyAnalysisConfiguration" }
* // Override `isSource` and `isSink`.
@@ -41,7 +41,7 @@ private import TaintTrackingParameter::Private
* Then, to query whether there is flow between some `source` and `sink`,
* write
*
* ```
* ```ql
* exists(MyAnalysisConfiguration cfg | cfg.hasFlow(source, sink))
* ```
*
@@ -79,13 +79,6 @@ abstract class Configuration extends DataFlow::Configuration {
defaultTaintBarrier(node)
}
/** DEPRECATED: override `isSanitizerIn` and `isSanitizerOut` instead. */
deprecated predicate isSanitizerEdge(DataFlow::Node node1, DataFlow::Node node2) { none() }
deprecated final override predicate isBarrierEdge(DataFlow::Node node1, DataFlow::Node node2) {
isSanitizerEdge(node1, node2)
}
/** Holds if data flow into `node` is prohibited. */
predicate isSanitizerIn(DataFlow::Node node) { none() }

View File

@@ -26,7 +26,7 @@ private import TaintTrackingParameter::Private
* To create a configuration, extend this class with a subclass whose
* characteristic predicate is a unique singleton string. For example, write
*
* ```
* ```ql
* class MyAnalysisConfiguration extends TaintTracking::Configuration {
* MyAnalysisConfiguration() { this = "MyAnalysisConfiguration" }
* // Override `isSource` and `isSink`.
@@ -41,7 +41,7 @@ private import TaintTrackingParameter::Private
* Then, to query whether there is flow between some `source` and `sink`,
* write
*
* ```
* ```ql
* exists(MyAnalysisConfiguration cfg | cfg.hasFlow(source, sink))
* ```
*
@@ -79,13 +79,6 @@ abstract class Configuration extends DataFlow::Configuration {
defaultTaintBarrier(node)
}
/** DEPRECATED: override `isSanitizerIn` and `isSanitizerOut` instead. */
deprecated predicate isSanitizerEdge(DataFlow::Node node1, DataFlow::Node node2) { none() }
deprecated final override predicate isBarrierEdge(DataFlow::Node node1, DataFlow::Node node2) {
isSanitizerEdge(node1, node2)
}
/** Holds if data flow into `node` is prohibited. */
predicate isSanitizerIn(DataFlow::Node node) { none() }

View File

@@ -1,3 +1,11 @@
/**
* INTERNAL: This is part of the virtual dispatch computation.
*
* Provides a strengthening of the virtual dispatch relation using a dedicated
* data flow check for lambdas, anonymous classes, and other sufficiently
* private classes where all object instantiations are accounted for.
*/
import java
private import VirtualDispatch
private import semmle.code.java.dataflow.internal.BaseSSA
@@ -194,9 +202,7 @@ private predicate flowStep(RelevantNode n1, RelevantNode n2) {
or
n2.asExpr().(CastExpr).getExpr() = n1.asExpr()
or
n2.asExpr().(ConditionalExpr).getTrueExpr() = n1.asExpr()
or
n2.asExpr().(ConditionalExpr).getFalseExpr() = n1.asExpr()
n2.asExpr().(ChooseExpr).getAResultExpr() = n1.asExpr()
or
n2.asExpr().(AssignExpr).getSource() = n1.asExpr()
or

View File

@@ -100,9 +100,7 @@ private predicate step(Node n1, Node n2) {
or
n2.asExpr().(CastExpr).getExpr() = n1.asExpr()
or
n2.asExpr().(ConditionalExpr).getTrueExpr() = n1.asExpr()
or
n2.asExpr().(ConditionalExpr).getFalseExpr() = n1.asExpr()
n2.asExpr().(ChooseExpr).getAResultExpr() = n1.asExpr()
or
n2.asExpr().(AssignExpr).getSource() = n1.asExpr()
or

View File

@@ -1,3 +1,8 @@
/**
* Provides predicates for reasoning about runtime call targets through virtual
* dispatch.
*/
import java
import semmle.code.java.dataflow.TypeFlow
private import DispatchFlow as DispatchFlow
@@ -27,6 +32,7 @@ Callable exactCallable(Call c) {
private predicate implCount(MethodAccess m, int c) { strictcount(viableImpl(m)) = c }
/** Gets a viable implementation of the target of the given `Call`. */
Callable viableCallable(Call c) {
result = viableImpl(c)
or

View File

@@ -1,3 +1,8 @@
/**
* Provides classes and predicates for reasoning about calls that may invoke one
* of their arguments.
*/
import java
import VirtualDispatch

View File

@@ -7,7 +7,7 @@
import java
newtype AssertKind =
private newtype AssertKind =
AssertKindTrue() or
AssertKindFalse() or
AssertKindNotNull() or
@@ -50,6 +50,7 @@ private predicate assertionMethod(Method m, AssertKind kind) {
)
}
/** An assertion method. */
class AssertionMethod extends Method {
AssertionMethod() { assertionMethod(this, _) }

View File

@@ -1,31 +1,50 @@
/**
* Provides classes and predicates for working with annotations in the `javax` package.
*/
import java
/*
* javax.annotation annotations
* Annotations in the package `javax.annotation`.
*/
/**
* A `@javax.annotation.Generated` annotation.
*/
class GeneratedAnnotation extends Annotation {
GeneratedAnnotation() { this.getType().hasQualifiedName("javax.annotation", "Generated") }
}
/**
* A `@javax.annotation.PostConstruct` annotation.
*/
class PostConstructAnnotation extends Annotation {
PostConstructAnnotation() { this.getType().hasQualifiedName("javax.annotation", "PostConstruct") }
}
/**
* A `@javax.annotation.PreDestroy` annotation.
*/
class PreDestroyAnnotation extends Annotation {
PreDestroyAnnotation() { this.getType().hasQualifiedName("javax.annotation", "PreDestroy") }
}
/**
* A `@javax.annotation.Resource` annotation.
*/
class ResourceAnnotation extends Annotation {
ResourceAnnotation() { this.getType().hasQualifiedName("javax.annotation", "Resource") }
}
/**
* A `@javax.annotation.Resources` annotation.
*/
class ResourcesAnnotation extends Annotation {
ResourcesAnnotation() { this.getType().hasQualifiedName("javax.annotation", "Resources") }
}
/**
* A javax.annotation.ManagedBean annotation.
* A `@javax.annotation.ManagedBean` annotation.
*/
class JavaxManagedBeanAnnotation extends Annotation {
JavaxManagedBeanAnnotation() {
@@ -34,71 +53,104 @@ class JavaxManagedBeanAnnotation extends Annotation {
}
/*
* javax.annotation.security annotations
* Annotations in the package `javax.annotation.security`.
*/
/**
* A `@javax.annotation.security.DeclareRoles` annotation.
*/
class DeclareRolesAnnotation extends Annotation {
DeclareRolesAnnotation() {
this.getType().hasQualifiedName("javax.annotation.security", "DeclareRoles")
}
}
/**
* A `@javax.annotation.security.DenyAll` annotation.
*/
class DenyAllAnnotation extends Annotation {
DenyAllAnnotation() { this.getType().hasQualifiedName("javax.annotation.security", "DenyAll") }
}
/**
* A `@javax.annotation.security.PermitAll` annotation.
*/
class PermitAllAnnotation extends Annotation {
PermitAllAnnotation() {
this.getType().hasQualifiedName("javax.annotation.security", "PermitAll")
}
}
/**
* A `@javax.annotation.security.RolesAllowed` annotation.
*/
class RolesAllowedAnnotation extends Annotation {
RolesAllowedAnnotation() {
this.getType().hasQualifiedName("javax.annotation.security", "RolesAllowed")
}
}
/**
* A `@javax.annotation.security.RunAs` annotation.
*/
class RunAsAnnotation extends Annotation {
RunAsAnnotation() { this.getType().hasQualifiedName("javax.annotation.security", "RunAs") }
}
/*
* javax.interceptor annotations
* Annotations in the package `javax.interceptor`.
*/
/**
* A `@javax.interceptor.AroundInvoke` annotation.
*/
class AroundInvokeAnnotation extends Annotation {
AroundInvokeAnnotation() { this.getType().hasQualifiedName("javax.interceptor", "AroundInvoke") }
}
/**
* A `@javax.interceptor.ExcludeClassInterceptors` annotation.
*/
class ExcludeClassInterceptorsAnnotation extends Annotation {
ExcludeClassInterceptorsAnnotation() {
this.getType().hasQualifiedName("javax.interceptor", "ExcludeClassInterceptors")
}
}
/**
* A `@javax.interceptor.ExcludeDefaultInterceptors` annotation.
*/
class ExcludeDefaultInterceptorsAnnotation extends Annotation {
ExcludeDefaultInterceptorsAnnotation() {
this.getType().hasQualifiedName("javax.interceptor", "ExcludeDefaultInterceptors")
}
}
/**
* A `@javax.interceptor.Interceptors` annotation.
*/
class InterceptorsAnnotation extends Annotation {
InterceptorsAnnotation() { this.getType().hasQualifiedName("javax.interceptor", "Interceptors") }
}
/*
* javax.jws annotations
* Annotations in the package `javax.jws`.
*/
/**
* A `@javax.jws.WebService` annotation.
*/
class WebServiceAnnotation extends Annotation {
WebServiceAnnotation() { this.getType().hasQualifiedName("javax.jws", "WebService") }
}
/*
* javax.xml.ws annotations
* Annotations in the package `javax.xml.ws`.
*/
/**
* A `@javax.xml.ws.WebServiceRef` annotation.
*/
class WebServiceRefAnnotation extends Annotation {
WebServiceRefAnnotation() { this.getType().hasQualifiedName("javax.xml.ws", "WebServiceRef") }
}

View File

@@ -1,21 +1,25 @@
/*
/**
* Definitions related to `java.net.*`.
*/
import semmle.code.java.Type
/** The type `java.net.URLConnection`. */
class TypeUrlConnection extends RefType {
TypeUrlConnection() { hasQualifiedName("java.net", "URLConnection") }
}
/** The type `java.net.Socket`. */
class TypeSocket extends RefType {
TypeSocket() { hasQualifiedName("java.net", "Socket") }
}
/** The type `java.net.URL`. */
class TypeUrl extends RefType {
TypeUrl() { hasQualifiedName("java.net", "URL") }
}
/** The method `java.net.URLConnection::getInputStream`. */
class URLConnectionGetInputStreamMethod extends Method {
URLConnectionGetInputStreamMethod() {
getDeclaringType() instanceof TypeUrlConnection and
@@ -24,6 +28,7 @@ class URLConnectionGetInputStreamMethod extends Method {
}
}
/** The method `java.net.Socket::getInputStream`. */
class SocketGetInputStreamMethod extends Method {
SocketGetInputStreamMethod() {
getDeclaringType() instanceof TypeSocket and

View File

@@ -1,19 +1,3 @@
import java
/** A Spring framework annotation indicating remote user input from servlets. */
class SpringServletInputAnnotation extends Annotation {
SpringServletInputAnnotation() {
exists(AnnotationType a |
a = this.getType() and
a.getPackage().getName() = "org.springframework.web.bind.annotation"
|
a.hasName("MatrixVariable") or
a.hasName("RequestParam") or
a.hasName("RequestHeader") or
a.hasName("CookieValue") or
a.hasName("RequestPart") or
a.hasName("PathVariable") or
a.hasName("RequestBody")
)
}
}
import spring.SpringController
import spring.SpringWeb

View File

@@ -1,3 +1,5 @@
/** Provides classes and predicates for working with the GWT framework. */
import java
import GwtXml
import GwtUiBinder

View File

@@ -8,26 +8,44 @@
import java
import GwtUiBinderXml
/**
* An annotation in the package `com.google.gwt.uibinder.client`.
*/
class GwtUiBinderClientAnnotation extends Annotation {
GwtUiBinderClientAnnotation() { getType().getPackage().hasName("com.google.gwt.uibinder.client") }
}
/**
* A `@com.google.gwt.uibinder.client.UiHandler` annotation.
*/
class GwtUiHandlerAnnotation extends GwtUiBinderClientAnnotation {
GwtUiHandlerAnnotation() { getType().hasName("UiHandler") }
}
/**
* A `@com.google.gwt.uibinder.client.UiField` annotation.
*/
class GwtUiFieldAnnotation extends GwtUiBinderClientAnnotation {
GwtUiFieldAnnotation() { getType().hasName("UiField") }
}
/**
* A `@com.google.gwt.uibinder.client.UiTemplate` annotation.
*/
class GwtUiTemplateAnnotation extends GwtUiBinderClientAnnotation {
GwtUiTemplateAnnotation() { getType().hasName("UiTemplate") }
}
/**
* A `@com.google.gwt.uibinder.client.UiFactory` annotation.
*/
class GwtUiFactoryAnnotation extends GwtUiBinderClientAnnotation {
GwtUiFactoryAnnotation() { getType().hasName("UiFactory") }
}
/**
* A `@com.google.gwt.uibinder.client.UiConstructor` annotation.
*/
class GwtUiConstructorAnnotation extends GwtUiBinderClientAnnotation {
GwtUiConstructorAnnotation() { getType().hasName("UiConstructor") }
}

View File

@@ -1,3 +1,5 @@
/** Provides classes and predicates for working with `*.gwt.xml` files. */
import semmle.code.xml.XML
/**

View File

@@ -1,3 +1,7 @@
/**
* Provides classes and predicates for working with OCNI (Objective-C Native Interface).
*/
import java
/**

View File

@@ -0,0 +1,23 @@
/**
* Provides classes and predicates for working with the jOOQ framework.
*/
import java
/**
* Methods annotated with this allow for generation of "plain SQL"
* and is prone to SQL injection.
* https://www.jooq.org/doc/current/manual/sql-building/plain-sql/
*/
private class PlainSQLType extends Annotation {
PlainSQLType() { this.getType().hasQualifiedName("org.jooq", "PlainSQL") }
}
/**
* Holds if `m` is a jOOQ SQL method taking an SQL string as its
* first argument.
*/
predicate jOOQSqlMethod(Method m) {
m.getAnAnnotation() instanceof PlainSQLType and
m.getParameterType(0) instanceof TypeString
}

View File

@@ -9,6 +9,9 @@ import semmle.code.java.Reflection
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.DataFlow5
/**
* A `@com.fasterxml.jackson.annotation.JsonIgnore` annoation.
*/
class JacksonJSONIgnoreAnnotation extends NonReflectiveAnnotation {
JacksonJSONIgnoreAnnotation() {
exists(AnnotationType anntp | anntp = this.getType() |
@@ -17,6 +20,7 @@ class JacksonJSONIgnoreAnnotation extends NonReflectiveAnnotation {
}
}
/** A type whose values may be serialized using the Jackson JSON framework. */
abstract class JacksonSerializableType extends Type { }
/**
@@ -34,6 +38,7 @@ library class JacksonWriteValueMethod extends Method {
}
}
/** A type whose values are explicitly serialized in a call to a Jackson method. */
library class ExplicitlyWrittenJacksonSerializableType extends JacksonSerializableType {
ExplicitlyWrittenJacksonSerializableType() {
exists(MethodAccess ma |
@@ -45,12 +50,14 @@ library class ExplicitlyWrittenJacksonSerializableType extends JacksonSerializab
}
}
/** A type used in a `JacksonSerializableField` declaration. */
library class FieldReferencedJacksonSerializableType extends JacksonSerializableType {
FieldReferencedJacksonSerializableType() {
exists(JacksonSerializableField f | usesType(f.getType(), this))
}
}
/** A type whose values may be deserialized by the Jackson JSON framework. */
abstract class JacksonDeserializableType extends Type { }
private class TypeLiteralToJacksonDatabindFlowConfiguration extends DataFlow5::Configuration {
@@ -76,6 +83,7 @@ private class TypeLiteralToJacksonDatabindFlowConfiguration extends DataFlow5::C
TypeLiteral getSourceWithFlowToJacksonDatabind() { hasFlow(DataFlow::exprNode(result), _) }
}
/** A type whose values are explicitly deserialized in a call to a Jackson method. */
library class ExplicitlyReadJacksonDeserializableType extends JacksonDeserializableType {
ExplicitlyReadJacksonDeserializableType() {
exists(TypeLiteralToJacksonDatabindFlowConfiguration conf |
@@ -84,12 +92,14 @@ library class ExplicitlyReadJacksonDeserializableType extends JacksonDeserializa
}
}
/** A type used in a `JacksonDeserializableField` declaration. */
library class FieldReferencedJacksonDeSerializableType extends JacksonDeserializableType {
FieldReferencedJacksonDeSerializableType() {
exists(JacksonDeserializableField f | usesType(f.getType(), this))
}
}
/** A field that may be serialized using the Jackson JSON framework. */
class JacksonSerializableField extends SerializableField {
JacksonSerializableField() {
exists(JacksonSerializableType superType |
@@ -101,6 +111,7 @@ class JacksonSerializableField extends SerializableField {
}
}
/** A field that may be deserialized using the Jackson JSON framework. */
class JacksonDeserializableField extends DeserializableField {
JacksonDeserializableField() {
exists(JacksonDeserializableType superType |
@@ -183,6 +194,7 @@ class JacksonMixinType extends ClassOrInterface {
}
}
/** A callable used as a Jackson mixin callable. */
class JacksonMixedInCallable extends Callable {
JacksonMixedInCallable() {
exists(JacksonMixinType mixinType | this = mixinType.getAMixedInCallable())

View File

@@ -1,3 +1,5 @@
/** Provides classes and predicates for working with Java Server Faces. */
import default
import semmle.code.java.frameworks.javaee.jsf.JSFAnnotations
import semmle.code.java.frameworks.javaee.jsf.JSFFacesContextXML
@@ -42,6 +44,7 @@ class FacesAccessibleType extends RefType {
)
}
/** Gets a method declared on this type that is visible to JSF. */
FacesVisibleMethod getAnAccessibleMethod() { result = getAMethod() }
}

View File

@@ -1,3 +1,7 @@
/**
* Provides classes and predicates for working with the JavaEE Persistence API.
*/
import java
/**
@@ -29,7 +33,7 @@ class PersistentEntity extends RefType {
}
/**
* If there is an annotation on this class defining the access type, then this is the type.
* Gets the access type for this entity as defined by a `@javax.persistence.Access` annotation, if any.
*/
string getAccessTypeFromAnnotation() {
exists(AccessAnnotation accessType | accessType = getAnAnnotation() |
@@ -43,376 +47,607 @@ class PersistentEntity extends RefType {
* Annotations in the `javax.persistence` package.
*/
/**
* A `@javax.persistence.Access` annotation.
*/
class AccessAnnotation extends Annotation {
AccessAnnotation() { this.getType().hasQualifiedName("javax.persistence", "Access") }
}
/**
* A `@javax.persistence.AccessType` annotation.
*/
class AccessTypeAnnotation extends Annotation {
AccessTypeAnnotation() { this.getType().hasQualifiedName("javax.persistence", "AccessType") }
}
/**
* A `@javax.persistence.AssociationOverride` annotation.
*/
class AssociationOverrideAnnotation extends Annotation {
AssociationOverrideAnnotation() {
this.getType().hasQualifiedName("javax.persistence", "AssociationOverride")
}
}
/**
* A `@javax.persistence.AssociationOverrides` annotation.
*/
class AssociationOverridesAnnotation extends Annotation {
AssociationOverridesAnnotation() {
this.getType().hasQualifiedName("javax.persistence", "AssociationOverrides")
}
}
/**
* A `@javax.persistence.AttributeOverride` annotation.
*/
class AttributeOverrideAnnotation extends Annotation {
AttributeOverrideAnnotation() {
this.getType().hasQualifiedName("javax.persistence", "AttributeOverride")
}
}
/**
* A `@javax.persistence.AttributeOverrides` annotation.
*/
class AttributeOverridesAnnotation extends Annotation {
AttributeOverridesAnnotation() {
this.getType().hasQualifiedName("javax.persistence", "AttributeOverrides")
}
}
/**
* A `@javax.persistence.Basic` annotation.
*/
class BasicAnnotation extends Annotation {
BasicAnnotation() { this.getType().hasQualifiedName("javax.persistence", "Basic") }
}
/**
* A `@javax.persistence.Cacheable` annotation.
*/
class CacheableAnnotation extends Annotation {
CacheableAnnotation() { this.getType().hasQualifiedName("javax.persistence", "Cacheable") }
}
/**
* A `@javax.persistence.CollectionTable` annotation.
*/
class CollectionTableAnnotation extends Annotation {
CollectionTableAnnotation() {
this.getType().hasQualifiedName("javax.persistence", "CollectionTable")
}
}
/**
* A `@javax.persistence.Column` annotation.
*/
class ColumnAnnotation extends Annotation {
ColumnAnnotation() { this.getType().hasQualifiedName("javax.persistence", "Column") }
}
/**
* A `@javax.persistence.ColumnResult` annotation.
*/
class ColumnResultAnnotation extends Annotation {
ColumnResultAnnotation() { this.getType().hasQualifiedName("javax.persistence", "ColumnResult") }
}
/**
* A `@javax.persistence.DiscriminatorColumn` annotation.
*/
class DiscriminatorColumnAnnotation extends Annotation {
DiscriminatorColumnAnnotation() {
this.getType().hasQualifiedName("javax.persistence", "DiscriminatorColumn")
}
}
/**
* A `@javax.persistence.DiscriminatorValue` annotation.
*/
class DiscriminatorValueAnnotation extends Annotation {
DiscriminatorValueAnnotation() {
this.getType().hasQualifiedName("javax.persistence", "DiscriminatorValue")
}
}
/**
* A `@javax.persistence.ElementCollection` annotation.
*/
class ElementCollectionAnnotation extends Annotation {
ElementCollectionAnnotation() {
this.getType().hasQualifiedName("javax.persistence", "ElementCollection")
}
}
/**
* A `@javax.persistence.Embeddable` annotation.
*/
class EmbeddableAnnotation extends Annotation {
EmbeddableAnnotation() { this.getType().hasQualifiedName("javax.persistence", "Embeddable") }
}
/**
* A `@javax.persistence.Embedded` annotation.
*/
class EmbeddedAnnotation extends Annotation {
EmbeddedAnnotation() { this.getType().hasQualifiedName("javax.persistence", "Embedded") }
}
/**
* A `@javax.persistence.EmbeddedId` annotation.
*/
class EmbeddedIdAnnotation extends Annotation {
EmbeddedIdAnnotation() { this.getType().hasQualifiedName("javax.persistence", "EmbeddedId") }
}
/**
* A `@javax.persistence.Entity` annotation.
*/
class EntityAnnotation extends Annotation {
EntityAnnotation() { this.getType().hasQualifiedName("javax.persistence", "Entity") }
}
/**
* A `@javax.persistence.EntityListeners` annotation.
*/
class EntityListenersAnnotation extends Annotation {
EntityListenersAnnotation() {
this.getType().hasQualifiedName("javax.persistence", "EntityListeners")
}
}
/**
* A `@javax.persistence.EntityResult` annotation.
*/
class EntityResultAnnotation extends Annotation {
EntityResultAnnotation() { this.getType().hasQualifiedName("javax.persistence", "EntityResult") }
}
/**
* A `@javax.persistence.Enumerated` annotation.
*/
class EnumeratedAnnotation extends Annotation {
EnumeratedAnnotation() { this.getType().hasQualifiedName("javax.persistence", "Enumerated") }
}
/**
* A `@javax.persistence.ExcludeDefaultListeners` annotation.
*/
class ExcludeDefaultListenersAnnotation extends Annotation {
ExcludeDefaultListenersAnnotation() {
this.getType().hasQualifiedName("javax.persistence", "ExcludeDefaultListeners")
}
}
/**
* A `@javax.persistence.ExcludeSuperclassListeners` annotation.
*/
class ExcludeSuperclassListenersAnnotation extends Annotation {
ExcludeSuperclassListenersAnnotation() {
this.getType().hasQualifiedName("javax.persistence", "ExcludeSuperclassListeners")
}
}
/**
* A `@javax.persistence.FieldResult` annotation.
*/
class FieldResultAnnotation extends Annotation {
FieldResultAnnotation() { this.getType().hasQualifiedName("javax.persistence", "FieldResult") }
}
/**
* A `@javax.persistence.GeneratedValue` annotation.
*/
class GeneratedValueAnnotation extends Annotation {
GeneratedValueAnnotation() {
this.getType().hasQualifiedName("javax.persistence", "GeneratedValue")
}
}
/**
* A `@javax.persistence.Id` annotation.
*/
class IdAnnotation extends Annotation {
IdAnnotation() { this.getType().hasQualifiedName("javax.persistence", "Id") }
}
/**
* A `@javax.persistence.IdClass` annotation.
*/
class IdClassAnnotation extends Annotation {
IdClassAnnotation() { this.getType().hasQualifiedName("javax.persistence", "IdClass") }
}
/**
* A `@javax.persistence.Inheritance` annotation.
*/
class InheritanceAnnotation extends Annotation {
InheritanceAnnotation() { this.getType().hasQualifiedName("javax.persistence", "Inheritance") }
}
/**
* A `@javax.persistence.JoinColumn` annotation.
*/
class JoinColumnAnnotation extends Annotation {
JoinColumnAnnotation() { this.getType().hasQualifiedName("javax.persistence", "JoinColumn") }
}
/**
* A `@javax.persistence.JoinColumns` annotation.
*/
class JoinColumnsAnnotation extends Annotation {
JoinColumnsAnnotation() { this.getType().hasQualifiedName("javax.persistence", "JoinColumns") }
}
/**
* A `@javax.persistence.JoinTable` annotation.
*/
class JoinTableAnnotation extends Annotation {
JoinTableAnnotation() { this.getType().hasQualifiedName("javax.persistence", "JoinTable") }
}
/**
* A `@javax.persistence.Lob` annotation.
*/
class LobAnnotation extends Annotation {
LobAnnotation() { this.getType().hasQualifiedName("javax.persistence", "Lob") }
}
/**
* A `@javax.persistence.ManyToMany` annotation.
*/
class ManyToManyAnnotation extends Annotation {
ManyToManyAnnotation() { this.getType().hasQualifiedName("javax.persistence", "ManyToMany") }
}
/**
* A `@javax.persistence.ManyToOne` annotation.
*/
class ManyToOneAnnotation extends Annotation {
ManyToOneAnnotation() { this.getType().hasQualifiedName("javax.persistence", "ManyToOne") }
}
/**
* A `@javax.persistence.MapKey` annotation.
*/
class MapKeyAnnotation extends Annotation {
MapKeyAnnotation() { this.getType().hasQualifiedName("javax.persistence", "MapKey") }
}
/**
* A `@javax.persistence.MapKeyClass` annotation.
*/
class MapKeyClassAnnotation extends Annotation {
MapKeyClassAnnotation() { this.getType().hasQualifiedName("javax.persistence", "MapKeyClass") }
}
/**
* A `@javax.persistence.MapKeyColumn` annotation.
*/
class MapKeyColumnAnnotation extends Annotation {
MapKeyColumnAnnotation() { this.getType().hasQualifiedName("javax.persistence", "MapKeyColumn") }
}
/**
* A `@javax.persistence.MapKeyEnumerated` annotation.
*/
class MapKeyEnumeratedAnnotation extends Annotation {
MapKeyEnumeratedAnnotation() {
this.getType().hasQualifiedName("javax.persistence", "MapKeyEnumerated")
}
}
/**
* A `@javax.persistence.MapKeyJoinColumn` annotation.
*/
class MapKeyJoinColumnAnnotation extends Annotation {
MapKeyJoinColumnAnnotation() {
this.getType().hasQualifiedName("javax.persistence", "MapKeyJoinColumn")
}
}
/**
* A `@javax.persistence.MapKeyJoinColumns` annotation.
*/
class MapKeyJoinColumnsAnnotation extends Annotation {
MapKeyJoinColumnsAnnotation() {
this.getType().hasQualifiedName("javax.persistence", "MapKeyJoinColumns")
}
}
/**
* A `@javax.persistence.MapKeyTemporal` annotation.
*/
class MapKeyTemporalAnnotation extends Annotation {
MapKeyTemporalAnnotation() {
this.getType().hasQualifiedName("javax.persistence", "MapKeyTemporal")
}
}
/**
* A `@javax.persistence.MappedSuperclass` annotation.
*/
class MappedSuperclassAnnotation extends Annotation {
MappedSuperclassAnnotation() {
this.getType().hasQualifiedName("javax.persistence", "MappedSuperclass")
}
}
/**
* A `@javax.persistence.MapsId` annotation.
*/
class MapsIdAnnotation extends Annotation {
MapsIdAnnotation() { this.getType().hasQualifiedName("javax.persistence", "MapsId") }
}
/**
* A `@javax.persistence.NamedNativeQueries` annotation.
*/
class NamedNativeQueriesAnnotation extends Annotation {
NamedNativeQueriesAnnotation() {
this.getType().hasQualifiedName("javax.persistence", "NamedNativeQueries")
}
}
/**
* A `@javax.persistence.NamedNativeQuery` annotation.
*/
class NamedNativeQueryAnnotation extends Annotation {
NamedNativeQueryAnnotation() {
this.getType().hasQualifiedName("javax.persistence", "NamedNativeQuery")
}
}
/**
* A `@javax.persistence.NamedQueries` annotation.
*/
class NamedQueriesAnnotation extends Annotation {
NamedQueriesAnnotation() { this.getType().hasQualifiedName("javax.persistence", "NamedQueries") }
}
/**
* A `@javax.persistence.NamedQuery` annotation.
*/
class NamedQueryAnnotation extends Annotation {
NamedQueryAnnotation() { this.getType().hasQualifiedName("javax.persistence", "NamedQuery") }
}
/**
* A `@javax.persistence.OneToMany` annotation.
*/
class OneToManyAnnotation extends Annotation {
OneToManyAnnotation() { this.getType().hasQualifiedName("javax.persistence", "OneToMany") }
}
/**
* A `@javax.persistence.OneToOne` annotation.
*/
class OneToOneAnnotation extends Annotation {
OneToOneAnnotation() { this.getType().hasQualifiedName("javax.persistence", "OneToOne") }
}
/**
* A `@javax.persistence.OrderBy` annotation.
*/
class OrderByAnnotation extends Annotation {
OrderByAnnotation() { this.getType().hasQualifiedName("javax.persistence", "OrderBy") }
}
/**
* A `@javax.persistence.OrderColumn` annotation.
*/
class OrderColumnAnnotation extends Annotation {
OrderColumnAnnotation() { this.getType().hasQualifiedName("javax.persistence", "OrderColumn") }
}
/**
* A `@javax.persistence.PersistenceContext` annotation.
*/
class PersistenceContextAnnotation extends Annotation {
PersistenceContextAnnotation() {
this.getType().hasQualifiedName("javax.persistence", "PersistenceContext")
}
}
/**
* A `@javax.persistence.PersistenceContexts` annotation.
*/
class PersistenceContextsAnnotation extends Annotation {
PersistenceContextsAnnotation() {
this.getType().hasQualifiedName("javax.persistence", "PersistenceContexts")
}
}
/**
* A `@javax.persistence.PersistenceProperty` annotation.
*/
class PersistencePropertyAnnotation extends Annotation {
PersistencePropertyAnnotation() {
this.getType().hasQualifiedName("javax.persistence", "PersistenceProperty")
}
}
/**
* A `@javax.persistence.PersistenceUnit` annotation.
*/
class PersistenceUnitAnnotation extends Annotation {
PersistenceUnitAnnotation() {
this.getType().hasQualifiedName("javax.persistence", "PersistenceUnit")
}
}
/**
* A `@javax.persistence.PersistenceUnits` annotation.
*/
class PersistenceUnitsAnnotation extends Annotation {
PersistenceUnitsAnnotation() {
this.getType().hasQualifiedName("javax.persistence", "PersistenceUnits")
}
}
/**
* A `@javax.persistence.PostLoad` annotation.
*/
class PostLoadAnnotation extends Annotation {
PostLoadAnnotation() { this.getType().hasQualifiedName("javax.persistence", "PostLoad") }
}
/**
* A `@javax.persistence.PostPersist` annotation.
*/
class PostPersistAnnotation extends Annotation {
PostPersistAnnotation() { this.getType().hasQualifiedName("javax.persistence", "PostPersist") }
}
/**
* A `@javax.persistence.PostRemove` annotation.
*/
class PostRemoveAnnotation extends Annotation {
PostRemoveAnnotation() { this.getType().hasQualifiedName("javax.persistence", "PostRemove") }
}
/**
* A `@javax.persistence.PostUpdate` annotation.
*/
class PostUpdateAnnotation extends Annotation {
PostUpdateAnnotation() { this.getType().hasQualifiedName("javax.persistence", "PostUpdate") }
}
/**
* A `@javax.persistence.PrePersist` annotation.
*/
class PrePersistAnnotation extends Annotation {
PrePersistAnnotation() { this.getType().hasQualifiedName("javax.persistence", "PrePersist") }
}
/**
* A `@javax.persistence.PreRemove` annotation.
*/
class PreRemoveAnnotation extends Annotation {
PreRemoveAnnotation() { this.getType().hasQualifiedName("javax.persistence", "PreRemove") }
}
/**
* A `@javax.persistence.PreUpdate` annotation.
*/
class PreUpdateAnnotation extends Annotation {
PreUpdateAnnotation() { this.getType().hasQualifiedName("javax.persistence", "PreUpdate") }
}
/**
* A `@javax.persistence.PrimaryKeyJoinColumn` annotation.
*/
class PrimaryKeyJoinColumnAnnotation extends Annotation {
PrimaryKeyJoinColumnAnnotation() {
this.getType().hasQualifiedName("javax.persistence", "PrimaryKeyJoinColumn")
}
}
/**
* A `@javax.persistence.PrimaryKeyJoinColumns` annotation.
*/
class PrimaryKeyJoinColumnsAnnotation extends Annotation {
PrimaryKeyJoinColumnsAnnotation() {
this.getType().hasQualifiedName("javax.persistence", "PrimaryKeyJoinColumns")
}
}
/**
* A `@javax.persistence.QueryHint` annotation.
*/
class QueryHintAnnotation extends Annotation {
QueryHintAnnotation() { this.getType().hasQualifiedName("javax.persistence", "QueryHint") }
}
/**
* A `@javax.persistence.SecondaryTable` annotation.
*/
class SecondaryTableAnnotation extends Annotation {
SecondaryTableAnnotation() {
this.getType().hasQualifiedName("javax.persistence", "SecondaryTable")
}
}
/**
* A `@javax.persistence.SecondaryTables` annotation.
*/
class SecondaryTablesAnnotation extends Annotation {
SecondaryTablesAnnotation() {
this.getType().hasQualifiedName("javax.persistence", "SecondaryTables")
}
}
/**
* A `@javax.persistence.SequenceGenerator` annotation.
*/
class SequenceGeneratorAnnotation extends Annotation {
SequenceGeneratorAnnotation() {
this.getType().hasQualifiedName("javax.persistence", "SequenceGenerator")
}
}
/**
* A `@javax.persistence.SqlResultSetMapping` annotation.
*/
class SqlResultSetMappingAnnotation extends Annotation {
SqlResultSetMappingAnnotation() {
this.getType().hasQualifiedName("javax.persistence", "SqlResultSetMapping")
}
}
/**
* A `@javax.persistence.SqlResultSetMappings` annotation.
*/
class SqlResultSetMappingsAnnotation extends Annotation {
SqlResultSetMappingsAnnotation() {
this.getType().hasQualifiedName("javax.persistence", "SqlResultSetMappings")
}
}
/**
* A `@javax.persistence.Table` annotation.
*/
class TableAnnotation extends Annotation {
TableAnnotation() { this.getType().hasQualifiedName("javax.persistence", "Table") }
}
/**
* A `@javax.persistence.TableGenerator` annotation.
*/
class TableGeneratorAnnotation extends Annotation {
TableGeneratorAnnotation() {
this.getType().hasQualifiedName("javax.persistence", "TableGenerator")
}
}
/**
* A `@javax.persistence.Temporal` annotation.
*/
class TemporalAnnotation extends Annotation {
TemporalAnnotation() { this.getType().hasQualifiedName("javax.persistence", "Temporal") }
}
/**
* A `@javax.persistence.Transient` annotation.
*/
class TransientAnnotation extends Annotation {
TransientAnnotation() { this.getType().hasQualifiedName("javax.persistence", "Transient") }
}
/**
* A `@javax.persistence.UniqueConstraint` annotation.
*/
class UniqueConstraintAnnotation extends Annotation {
UniqueConstraintAnnotation() {
this.getType().hasQualifiedName("javax.persistence", "UniqueConstraint")
}
}
/**
* A `@javax.persistence.Version` annotation.
*/
class VersionAnnotation extends Annotation {
VersionAnnotation() { this.getType().hasQualifiedName("javax.persistence", "Version") }
}

View File

@@ -1,3 +1,8 @@
/**
* Provides classes and predicates for working with JavaEE
* persistence configuration XML files (`persistence.xml`).
*/
import java
/**
@@ -6,66 +11,94 @@ import java
class PersistenceXMLFile extends XMLFile {
PersistenceXMLFile() { this.getStem() = "persistence" }
/** Gets the root XML element in this `persistence.xml` file. */
PersistenceXmlRoot getRoot() { result = this.getAChild() }
// convenience methods
/** Gets a `shared-cache-mode` XML element nested within this `persistence.xml` file. */
SharedCacheModeElement getASharedCacheModeElement() {
result = this.getRoot().getAPersistenceUnitElement().getASharedCacheModeElement()
}
/** Gets a `property` XML element nested within this `persistence.xml` file. */
PersistencePropertyElement getAPropertyElement() {
result =
this.getRoot().getAPersistenceUnitElement().getAPropertiesElement().getAPropertyElement()
}
}
/** The root `persistence` XML element in a `persistence.xml` file. */
class PersistenceXmlRoot extends XMLElement {
PersistenceXmlRoot() {
this.getParent() instanceof PersistenceXMLFile and
this.getName() = "persistence"
}
/** Gets a `persistence-unit` child XML element of this `persistence` XML element. */
PersistenceUnitElement getAPersistenceUnitElement() { result = this.getAChild() }
}
/**
* A `persistence-unit` child XML element of the root
* `persistence` XML element in a `persistence.xml` file.
*/
class PersistenceUnitElement extends XMLElement {
PersistenceUnitElement() {
this.getParent() instanceof PersistenceXmlRoot and
this.getName() = "persistence-unit"
}
/** Gets a `shared-cache-mode` child XML element of this `persistence-unit` XML element. */
SharedCacheModeElement getASharedCacheModeElement() { result = this.getAChild() }
/** Gets a `properties` child XML element of this `persistence-unit` XML element. */
PersistencePropertiesElement getAPropertiesElement() { result = this.getAChild() }
}
/**
* A `shared-cache-mode` child XML element of a `persistence-unit`
* XML element in a `persistence.xml` file.
*/
class SharedCacheModeElement extends XMLElement {
SharedCacheModeElement() {
this.getParent() instanceof PersistenceUnitElement and
this.getName() = "shared-cache-mode"
}
/** Gets the value of this `shared-cache-mode` XML element. */
string getValue() { result = this.getACharactersSet().getCharacters() }
/** Holds if this `shared-cache-mode` XML element has the value "NONE". */
predicate isDisabled() { this.getValue() = "NONE" }
}
/**
* A `properties` child XML element of a `persistence-unit`
* XML element in a `persistence.xml` file.
*/
class PersistencePropertiesElement extends XMLElement {
PersistencePropertiesElement() {
this.getParent() instanceof PersistenceUnitElement and
this.getName() = "properties"
}
/** Gets a `property` child XML element of this `properties` XML element. */
PersistencePropertyElement getAPropertyElement() { result = this.getAChild() }
}
/**
* A `property` child XML element of a `properties`
* XML element in a `persistence.xml` file.
*/
class PersistencePropertyElement extends XMLElement {
PersistencePropertyElement() {
this.getParent() instanceof PersistencePropertiesElement and
this.getName() = "property"
}
/** see http://wiki.eclipse.org/EclipseLink/Examples/JPA/Caching */
/**
* Holds if this `property` XML element of a `persistence.xml` file
* disables the EclipseLink shared cache.
*/
predicate disablesEclipseLinkSharedCache() {
getAttribute("name").getValue() = "eclipselink.cache.shared.default" and
getAttribute("value").getValue() = "false"

View File

@@ -1,3 +1,5 @@
/** Provides classes and predicates for working with Enterprise Java Beans. */
import java
import EJBJarXML
@@ -322,10 +324,17 @@ class LocalAnnotatedBusinessInterface extends AnnotatedBusinessInterface {
* Init and create methods for session beans.
*/
/**
* A `@javax.ejb.Init` annotation.
*/
class InitAnnotation extends Annotation {
InitAnnotation() { this.getType().hasQualifiedName("javax.ejb", "Init") }
}
/**
* A method annotated with a `@javax.ejb.Init` annotation
* that is declared in or inherited by a session EJB.
*/
class EjbAnnotatedInitMethod extends Method {
EjbAnnotatedInitMethod() {
this.getAnAnnotation() instanceof InitAnnotation and
@@ -333,21 +342,31 @@ class EjbAnnotatedInitMethod extends Method {
}
}
/**
* A method whose name starts with `ejbCreate` that is
* declared in or inherited by a session EJB.
*/
class EjbCreateMethod extends Method {
EjbCreateMethod() {
this.getName().matches("ejbCreate%") and
exists(SessionEJB ejb | ejb.inherits(this))
}
/** Gets the suffix of the method name without the `ejbCreate` prefix. */
string getMethodSuffix() { result = this.getName().substring(9, this.getName().length()) }
}
/**
* A method whose name starts with `create` that is
* declared in or inherited by a legacy EJB home interface.
*/
class EjbInterfaceCreateMethod extends Method {
EjbInterfaceCreateMethod() {
this.getName().matches("create%") and
exists(LegacyEjbHomeInterface i | i.inherits(this))
}
/** Gets the suffix of the method name without the `create` prefix. */
string getMethodSuffix() { result = this.getName().substring(6, this.getName().length()) }
}
@@ -398,6 +417,10 @@ class XmlSpecifiedRemoteInterface extends LegacyEjbRemoteInterface {
)
}
/**
* Gets a session EJB specified in the XML deployment descriptor
* for this legacy EJB remote interface.
*/
SessionEJB getAnEJB() {
exists(EjbJarXMLFile f, EjbJarSessionElement se |
se = f.getASessionElement() and
@@ -423,6 +446,7 @@ class AnnotatedRemoteHomeInterface extends LegacyEjbRemoteHomeInterface {
/** Gets an EJB to which this interface belongs. */
SessionEJB getAnEJB() { result.getAnAnnotation().(RemoteHomeAnnotation).getANamedType() = this }
/** Gets a remote interface associated with this legacy remote home interface. */
Interface getAnAssociatedRemoteInterface() { result = getACreateMethod().getReturnType() }
}
@@ -486,6 +510,7 @@ class AnnotatedLocalHomeInterface extends LegacyEjbLocalHomeInterface {
/** Gets an EJB to which this interface belongs. */
SessionEJB getAnEJB() { result.getAnAnnotation().(LocalHomeAnnotation).getANamedType() = this }
/** Gets a local interface associated with this legacy local home interface. */
Interface getAnAssociatedLocalInterface() { result = getACreateMethod().getReturnType() }
}
@@ -535,6 +560,7 @@ class RemoteInterface extends Interface {
*/
Method getARemoteMethod() { this.inherits(result) }
/** Gets a remote method implementation for this remote interface. */
Method getARemoteMethodImplementation() {
result = getARemoteMethodImplementationChecked() or
result = getARemoteMethodImplementationUnchecked()
@@ -716,127 +742,209 @@ Type inheritsMatchingCreateMethodExceptThrows(StatefulSessionEJB ejb, EjbInterfa
* Annotations in the `javax.ejb package`.
*/
/**
* A `@javax.ejb.AccessTimeout` annotation.
*/
class AccessTimeoutAnnotation extends Annotation {
AccessTimeoutAnnotation() { this.getType().hasQualifiedName("javax.ejb", "AccessTimeout") }
}
/**
* A `@javax.ejb.ActivationConfigProperty` annotation.
*/
class ActivationConfigPropertyAnnotation extends Annotation {
ActivationConfigPropertyAnnotation() {
this.getType().hasQualifiedName("javax.ejb", "ActivationConfigProperty")
}
}
/**
* A `@javax.ejb.AfterBegin` annotation.
*/
class AfterBeginAnnotation extends Annotation {
AfterBeginAnnotation() { this.getType().hasQualifiedName("javax.ejb", "AfterBegin") }
}
/**
* A `@javax.ejb.AfterCompletion` annotation.
*/
class AfterCompletionAnnotation extends Annotation {
AfterCompletionAnnotation() { this.getType().hasQualifiedName("javax.ejb", "AfterCompletion") }
}
/**
* A `@javax.ejb.ApplicationException` annotation.
*/
class ApplicationExceptionAnnotation extends Annotation {
ApplicationExceptionAnnotation() {
this.getType().hasQualifiedName("javax.ejb", "ApplicationException")
}
}
/**
* A `@javax.ejb.Asynchronous` annotation.
*/
class AsynchronousAnnotation extends Annotation {
AsynchronousAnnotation() { this.getType().hasQualifiedName("javax.ejb", "Asynchronous") }
}
/**
* A `@javax.ejb.BeforeCompletion` annotation.
*/
class BeforeCompletionAnnotation extends Annotation {
BeforeCompletionAnnotation() { this.getType().hasQualifiedName("javax.ejb", "BeforeCompletion") }
}
/**
* A `@javax.ejb.ConcurrencyManagement` annotation.
*/
class ConcurrencyManagementAnnotation extends Annotation {
ConcurrencyManagementAnnotation() {
this.getType().hasQualifiedName("javax.ejb", "ConcurrencyManagement")
}
}
/**
* A `@javax.ejb.DependsOn` annotation.
*/
class DependsOnAnnotation extends Annotation {
DependsOnAnnotation() { this.getType().hasQualifiedName("javax.ejb", "DependsOn") }
}
/**
* A `@javax.ejb.EJB` annotation.
*/
class EJBAnnotation extends Annotation {
EJBAnnotation() { this.getType().hasQualifiedName("javax.ejb", "EJB") }
}
/**
* A `@javax.ejb.EJBs` annotation.
*/
class EJBsAnnotation extends Annotation {
EJBsAnnotation() { this.getType().hasQualifiedName("javax.ejb", "EJBs") }
}
// See above for `@Init`, `@Local`.
/**
* A `@javax.ejb.LocalBean` annotation.
*/
class LocalBeanAnnotation extends Annotation {
LocalBeanAnnotation() { this.getType().hasQualifiedName("javax.ejb", "LocalBean") }
}
// See above for `@LocalHome`.
/**
* A `@javax.ejb.Lock` annotation.
*/
class LockAnnotation extends Annotation {
LockAnnotation() { this.getType().hasQualifiedName("javax.ejb", "Lock") }
}
/**
* A `@javax.ejb.MessageDriven` annotation.
*/
class MessageDrivenAnnotation extends Annotation {
MessageDrivenAnnotation() { this.getType().hasQualifiedName("javax.ejb", "MessageDriven") }
}
/**
* A `@javax.ejb.PostActivate` annotation.
*/
class PostActivateAnnotation extends Annotation {
PostActivateAnnotation() { this.getType().hasQualifiedName("javax.ejb", "PostActivate") }
}
/**
* A `@javax.ejb.PrePassivate` annotation.
*/
class PrePassivateAnnotation extends Annotation {
PrePassivateAnnotation() { this.getType().hasQualifiedName("javax.ejb", "PrePassivate") }
}
// See above for `@Remote`, `@RemoteHome`.
/**
* A `@javax.ejb.Remove` annotation.
*/
class RemoveAnnotation extends Annotation {
RemoveAnnotation() { this.getType().hasQualifiedName("javax.ejb", "Remove") }
}
/**
* A `@javax.ejb.Schedule` annotation.
*/
class ScheduleAnnotation extends Annotation {
ScheduleAnnotation() { this.getType().hasQualifiedName("javax.ejb", "Schedule") }
}
/**
* A `@javax.ejb.Schedules` annotation.
*/
class SchedulesAnnotation extends Annotation {
SchedulesAnnotation() { this.getType().hasQualifiedName("javax.ejb", "Schedules") }
}
/**
* A `@javax.ejb.Singleton` annotation.
*/
class SingletonAnnotation extends Annotation {
SingletonAnnotation() { this.getType().hasQualifiedName("javax.ejb", "Singleton") }
}
/**
* A `@javax.ejb.Startup` annotation.
*/
class StartupAnnotation extends Annotation {
StartupAnnotation() { this.getType().hasQualifiedName("javax.ejb", "Startup") }
}
/**
* A `@javax.ejb.Stateful` annotation.
*/
class StatefulAnnotation extends Annotation {
StatefulAnnotation() { this.getType().hasQualifiedName("javax.ejb", "Stateful") }
}
/**
* A `@javax.ejb.StatefulTimeout` annotation.
*/
class StatefulTimeoutAnnotation extends Annotation {
StatefulTimeoutAnnotation() { this.getType().hasQualifiedName("javax.ejb", "StatefulTimeout") }
}
/**
* A `@javax.ejb.Stateless` annotation.
*/
class StatelessAnnotation extends Annotation {
StatelessAnnotation() { this.getType().hasQualifiedName("javax.ejb", "Stateless") }
}
/**
* A `@javax.ejb.Timeout` annotation.
*/
class TimeoutAnnotation extends Annotation {
TimeoutAnnotation() { this.getType().hasQualifiedName("javax.ejb", "Timeout") }
}
/**
* A `@javax.ejb.TransactionAttribute` annotation.
*/
class TransactionAttributeAnnotation extends Annotation {
TransactionAttributeAnnotation() {
this.getType().hasQualifiedName("javax.ejb", "TransactionAttribute")
}
}
/**
* A `@javax.ejb.TransactionManagement` annotation.
*/
class TransactionManagementAnnotation extends Annotation {
TransactionManagementAnnotation() {
this.getType().hasQualifiedName("javax.ejb", "TransactionManagement")
}
}
/**
* A `@javax.ejb.TransactionAttribute` annotation with the
* transaction attribute type set to `REQUIRED`.
*/
class RequiredTransactionAttributeAnnotation extends TransactionAttributeAnnotation {
RequiredTransactionAttributeAnnotation() {
exists(FieldRead fr |
@@ -847,6 +955,10 @@ class RequiredTransactionAttributeAnnotation extends TransactionAttributeAnnotat
}
}
/**
* A `@javax.ejb.TransactionAttribute` annotation with the
* transaction attribute type set to `REQUIRES_NEW`.
*/
class RequiresNewTransactionAttributeAnnotation extends TransactionAttributeAnnotation {
RequiresNewTransactionAttributeAnnotation() {
exists(FieldRead fr |
@@ -861,6 +973,9 @@ class RequiresNewTransactionAttributeAnnotation extends TransactionAttributeAnno
* Convenience methods.
*/
/**
* Gets the innermost `@javax.ejb.TransactionAttribute` annotation for method `m`.
*/
TransactionAttributeAnnotation getInnermostTransactionAttributeAnnotation(Method m) {
// A `TransactionAttribute` annotation can either be on the method itself,
// in which case it supersedes any such annotation on the declaring class...
@@ -876,6 +991,10 @@ TransactionAttributeAnnotation getInnermostTransactionAttributeAnnotation(Method
* Methods in the `javax.ejb package`.
*/
/**
* A method named `setRollbackOnly` declared on the
* interface `javax.ejb.EJBContext` or a subtype thereof.
*/
class SetRollbackOnlyMethod extends Method {
SetRollbackOnlyMethod() {
this.getDeclaringType().getASupertype*().hasQualifiedName("javax.ejb", "EJBContext") and

View File

@@ -1,3 +1,8 @@
/**
* Provides classes and predicates for working with
* EJB deployment descriptor XML files (`ejb-jar.xml`).
*/
import java
/**
@@ -6,161 +11,222 @@ import java
class EjbJarXMLFile extends XMLFile {
EjbJarXMLFile() { this.getStem() = "ejb-jar" }
/** Gets the root `ejb-jar` XML element of this `ejb-jar.xml` file. */
EjbJarRootElement getRoot() { result = this.getAChild() }
// Convenience methods.
/** Gets an `enterprise-beans` XML element nested within this `ejb-jar.xml` file. */
EjbJarEnterpriseBeansElement getAnEnterpriseBeansElement() {
result = this.getRoot().getAnEnterpriseBeansElement()
}
/** Gets a `session` XML element nested within this `ejb-jar.xml` file. */
EjbJarSessionElement getASessionElement() {
result = this.getAnEnterpriseBeansElement().getASessionElement()
}
/** Gets a `message-driven` XML element nested within this `ejb-jar.xml` file. */
EjbJarMessageDrivenElement getAMessageDrivenElement() {
result = this.getAnEnterpriseBeansElement().getAMessageDrivenElement()
}
/** Gets an `entity` XML element nested within this `ejb-jar.xml` file. */
EjbJarEntityElement getAnEntityElement() {
result = this.getAnEnterpriseBeansElement().getAnEntityElement()
}
}
/** The root `ejb-jar` XML element in an `ejb-jar.xml` file. */
class EjbJarRootElement extends XMLElement {
EjbJarRootElement() {
this.getParent() instanceof EjbJarXMLFile and
this.getName() = "ejb-jar"
}
/** Gets an `enterprise-beans` child XML element of this root `ejb-jar` XML element. */
EjbJarEnterpriseBeansElement getAnEnterpriseBeansElement() { result = this.getAChild() }
}
/**
* An `enterprise-beans` child XML element of the root
* `ejb-jar` XML element in an `ejb-jar.xml` file.
*/
class EjbJarEnterpriseBeansElement extends XMLElement {
EjbJarEnterpriseBeansElement() {
this.getParent() instanceof EjbJarRootElement and
this.getName() = "enterprise-beans"
}
/** Gets a `session` child XML element of this `enterprise-beans` XML element. */
EjbJarSessionElement getASessionElement() {
result = this.getAChild() and
result.getName() = "session"
}
/** Gets a `message-driven` child XML element of this `enterprise-beans` XML element. */
EjbJarMessageDrivenElement getAMessageDrivenElement() {
result = this.getAChild() and
result.getName() = "message-driven"
}
/** Gets an `entity` child XML element of this `enterprise-beans` XML element. */
EjbJarEntityElement getAnEntityElement() {
result = this.getAChild() and
result.getName() = "entity"
}
}
/**
* A child XML element of an `enterprise-beans` XML element within an `ejb-jar.xml` file.
*
* This is either a `message-driven` element, a `session` element, or an `entity` element.
*/
abstract class EjbJarBeanTypeElement extends XMLElement {
EjbJarBeanTypeElement() { this.getParent() instanceof EjbJarEnterpriseBeansElement }
/** Gets an `ejb-class` child XML element of this bean type element. */
XMLElement getAnEjbClassElement() {
result = this.getAChild() and
result.getName() = "ejb-class"
}
}
/**
* A `session` child XML element of a bean type element in an `ejb-jar.xml` file.
*/
class EjbJarSessionElement extends EjbJarBeanTypeElement {
EjbJarSessionElement() { this.getName() = "session" }
/** Gets a `business-local` child XML element of this `session` XML element. */
XMLElement getABusinessLocalElement() {
result = this.getAChild() and
result.getName() = "business-local"
}
/** Gets a `business-remote` child XML element of this `session` XML element. */
XMLElement getABusinessRemoteElement() {
result = this.getAChild() and
result.getName() = "business-remote"
}
/**
* Gets a business child XML element of this `session` XML element.
*
* This is either a `business-local` or `business-remote` element.
*/
XMLElement getABusinessElement() {
result = getABusinessLocalElement() or
result = getABusinessRemoteElement()
}
/** Gets a `remote` child XML element of this `session` XML element. */
XMLElement getARemoteElement() {
result = this.getAChild() and
result.getName() = "remote"
}
/** Gets a `home` child XML element of this `session` XML element. */
XMLElement getARemoteHomeElement() {
result = this.getAChild() and
result.getName() = "home"
}
/** Gets a `local` child XML element of this `session` XML element. */
XMLElement getALocalElement() {
result = this.getAChild() and
result.getName() = "local"
}
/** Gets a `local-home` child XML element of this `session` XML element. */
XMLElement getALocalHomeElement() {
result = this.getAChild() and
result.getName() = "local-home"
}
/** Gets a `session-type` child XML element of this `session` XML element. */
EjbJarSessionTypeElement getASessionTypeElement() { result = this.getAChild() }
/** Gets an `init-method` child XML element of this `session` XML element. */
EjbJarInitMethodElement getAnInitMethodElement() { result = this.getAChild() }
// Convenience methods.
/**
* Gets a `method-name` child XML element of a `create-method`
* XML element nested within this `session` XML element.
*/
XMLElement getACreateMethodNameElement() {
result = getAnInitMethodElement().getACreateMethodElement().getAMethodNameElement()
}
/**
* Gets a `method-name` child XML element of a `bean-method`
* XML element nested within this `session` XML element.
*/
XMLElement getABeanMethodNameElement() {
result = getAnInitMethodElement().getABeanMethodElement().getAMethodNameElement()
}
}
/**
* A `message-driven` child XML element of a bean type element in an `ejb-jar.xml` file.
*/
class EjbJarMessageDrivenElement extends EjbJarBeanTypeElement {
EjbJarMessageDrivenElement() { this.getName() = "message-driven" }
}
/**
* An `entity` child XML element of a bean type element in an `ejb-jar.xml` file.
*/
class EjbJarEntityElement extends EjbJarBeanTypeElement {
EjbJarEntityElement() { this.getName() = "entity" }
}
/** A `session-type` child XML element of a `session` element in an `ejb-jar.xml` file. */
class EjbJarSessionTypeElement extends XMLElement {
EjbJarSessionTypeElement() {
this.getParent() instanceof EjbJarSessionElement and
this.getName() = "session-type"
}
/** Holds if the value of this `session-type` XML element is "Stateful". */
predicate isStateful() { this.getACharactersSet().getCharacters() = "Stateful" }
/** Holds if the value of this `session-type` XML element is "Stateless". */
predicate isStateless() { this.getACharactersSet().getCharacters() = "Stateless" }
}
/** An `init-method` child XML element of a `session` element in an `ejb-jar.xml` file. */
class EjbJarInitMethodElement extends XMLElement {
EjbJarInitMethodElement() {
this.getParent() instanceof EjbJarSessionElement and
this.getName() = "init-method"
}
/** Gets a `create-method` child XML element of this `init-method` XML element. */
EjbJarCreateMethodElement getACreateMethodElement() {
result = this.getAChild() and
result.getName() = "create-method"
}
/** Gets a `bean-method` child XML element of this `init-method` XML element. */
EjbJarBeanMethodElement getABeanMethodElement() {
result = this.getAChild() and
result.getName() = "bean-method"
}
}
/**
* A child XML element of an `init-method` element in an `ejb-jar.xml` file.
*
* This is either a `create-method` element, or a `bean-method` element.
*/
abstract class EjbJarInitMethodChildElement extends XMLElement {
/** Gets a `method-name` child XML element of this `create-method` or `bean-method` XML element. */
XMLElement getAMethodNameElement() {
result = this.getAChild() and
result.getName() = "method-name"
}
}
/** A `create-method` child XML element of an `init-method` element in an `ejb-jar.xml` file. */
class EjbJarCreateMethodElement extends EjbJarInitMethodChildElement {
EjbJarCreateMethodElement() {
this.getParent() instanceof EjbJarInitMethodElement and
@@ -168,6 +234,7 @@ class EjbJarCreateMethodElement extends EjbJarInitMethodChildElement {
}
}
/** A `bean-method` child XML element of an `init-method` element in an `ejb-jar.xml` file. */
class EjbJarBeanMethodElement extends EjbJarInitMethodChildElement {
EjbJarBeanMethodElement() {
this.getParent() instanceof EjbJarInitMethodElement and

View File

@@ -1,10 +1,12 @@
import java
import EJB
/*
/**
* Provides classes and predicates for modeling
* EJB Programming Restrictions (see EJB 3.0 specification, section 21.1.2).
*/
import java
import EJB
/** A method or constructor that may not be called from an EJB. */
abstract class ForbiddenCallable extends Callable { }
/**
@@ -47,6 +49,7 @@ predicate ejbCalls(Callable origin, ForbiddenCallable target, Call call) {
* Specification of "forbidden callables".
*/
/** A method or constructor that may not be called by an EJB due to container interference. */
class ForbiddenContainerInterferenceCallable extends ForbiddenCallable {
ForbiddenContainerInterferenceCallable() {
this.getDeclaringType().getASupertype*().getSourceDeclaration() instanceof ClassLoaderClass or
@@ -55,18 +58,21 @@ class ForbiddenContainerInterferenceCallable extends ForbiddenCallable {
}
}
/** A method or constructor involving file input or output that may not be called by an EJB. */
class ForbiddenFileCallable extends ForbiddenCallable {
ForbiddenFileCallable() {
this.getDeclaringType().getASupertype*().getSourceDeclaration() instanceof FileInputOutputClass
}
}
/** A method or constructor involving graphics operations that may not be called by an EJB. */
class ForbiddenGraphicsCallable extends ForbiddenCallable {
ForbiddenGraphicsCallable() {
this.getDeclaringType().getASupertype*().getPackage() instanceof GraphicsPackage
}
}
/** A method or constructor involving native code that may not be called by an EJB. */
class ForbiddenNativeCallable extends ForbiddenCallable {
ForbiddenNativeCallable() {
this.isNative() or
@@ -74,32 +80,38 @@ class ForbiddenNativeCallable extends ForbiddenCallable {
}
}
/** A method or constructor involving reflection that may not be called by and EJB. */
class ForbiddenReflectionCallable extends ForbiddenCallable {
ForbiddenReflectionCallable() {
this.getDeclaringType().getASupertype*().getPackage() instanceof ReflectionPackage
}
}
/** A method or constructor involving security configuration that may not be called by an EJB. */
class ForbiddenSecurityConfigurationCallable extends ForbiddenCallable {
ForbiddenSecurityConfigurationCallable() {
this.getDeclaringType().getASupertype*().getSourceDeclaration() instanceof SecurityConfigClass
}
}
/** A method or constructor involving serialization that may not be called by an EJB. */
class ForbiddenSerializationCallable extends ForbiddenCallable {
ForbiddenSerializationCallable() { this instanceof ForbiddenSerializationMethod }
}
/** A method or constructor involving network factory operations that may not be called by an EJB. */
class ForbiddenSetFactoryCallable extends ForbiddenCallable {
ForbiddenSetFactoryCallable() { this instanceof ForbiddenSetFactoryMethod }
}
/** A method or constructor involving server socket operations that may not be called by an EJB. */
class ForbiddenServerSocketCallable extends ForbiddenCallable {
ForbiddenServerSocketCallable() {
this.getDeclaringType().getASupertype*().getSourceDeclaration() instanceof ServerSocketsClass
}
}
/** A method or constructor involving synchronization that may not be called by an EJB. */
class ForbiddenSynchronizationCallable extends ForbiddenCallable {
ForbiddenSynchronizationCallable() {
this.isSynchronized()
@@ -112,26 +124,37 @@ class ForbiddenSynchronizationCallable extends ForbiddenCallable {
}
}
/** A method or constructor involving static field access that may not be called by an EJB. */
class ForbiddenStaticFieldCallable extends ForbiddenCallable {
ForbiddenStaticFieldCallable() { exists(forbiddenStaticFieldUse(this)) }
}
/**
* Gets an access to a non-final static field in callable `c`
* that is disallowed by the EJB specification.
*/
FieldAccess forbiddenStaticFieldUse(Callable c) {
result.getEnclosingCallable() = c and
result.getField().isStatic() and
not result.getField().isFinal()
}
/** A method or constructor involving thread operations that may not be called by an EJB. */
class ForbiddenThreadingCallable extends ForbiddenCallable {
ForbiddenThreadingCallable() {
this.getDeclaringType().getASupertype*().getSourceDeclaration() instanceof ThreadingClass
}
}
/** A method or constructor referencing `this` that may not be called by an EJB. */
class ForbiddenThisCallable extends ForbiddenCallable {
ForbiddenThisCallable() { exists(forbiddenThisUse(this)) }
}
/**
* Gets an access to `this` in callable `c`
* that is disallowed by the EJB specification.
*/
ThisAccess forbiddenThisUse(Callable c) {
result.getEnclosingCallable() = c and
(
@@ -144,6 +167,7 @@ ThisAccess forbiddenThisUse(Callable c) {
* Specification of "forbidden packages".
*/
/** The package `java.lang.reflect` or a subpackage thereof. */
class ReflectionPackage extends Package {
ReflectionPackage() {
this.getName() = "java.lang.reflect" or
@@ -151,6 +175,7 @@ class ReflectionPackage extends Package {
}
}
/** The package `java.awt` or `javax.swing` or a subpackage thereof. */
class GraphicsPackage extends Package {
GraphicsPackage() {
this.getName() = "java.awt" or
@@ -160,6 +185,7 @@ class GraphicsPackage extends Package {
}
}
/** The package `java.util.concurrent` or a subpackage thereof. */
class ConcurrentPackage extends Package {
ConcurrentPackage() {
this.getName() = "java.util.concurrent" or
@@ -171,6 +197,7 @@ class ConcurrentPackage extends Package {
* Specification of "forbidden classes".
*/
/** The class `java.lang.Thread` or `java.lang.ThreadGroup`. */
class ThreadingClass extends Class {
ThreadingClass() {
this.hasQualifiedName("java.lang", "Thread") or
@@ -178,6 +205,10 @@ class ThreadingClass extends Class {
}
}
/**
* The class `java.net.ServerSocket`, `java.net.MulticastSocket`
* or `java.nio.channels.ServerSocketChannel`.
*/
class ServerSocketsClass extends Class {
ServerSocketsClass() {
this.hasQualifiedName("java.net", "ServerSocket") or
@@ -186,6 +217,10 @@ class ServerSocketsClass extends Class {
}
}
/**
* A class in the package `java.security` named `Policy`,
* `Security`, `Provider`, `Signer` or `Identity`.
*/
class SecurityConfigClass extends Class {
SecurityConfigClass() {
this.hasQualifiedName("java.security", "Policy") or
@@ -196,14 +231,17 @@ class SecurityConfigClass extends Class {
}
}
/** The class `java.lang.ClassLoader`. */
class ClassLoaderClass extends Class {
ClassLoaderClass() { this.hasQualifiedName("java.lang", "ClassLoader") }
}
/** The class `java.lang.SecurityManager`. */
class SecurityManagerClass extends Class {
SecurityManagerClass() { this.hasQualifiedName("java.lang", "SecurityManager") }
}
/** A class involving file input or output. */
class FileInputOutputClass extends Class {
FileInputOutputClass() {
this.hasQualifiedName("java.io", "File") or
@@ -222,7 +260,7 @@ class FileInputOutputClass extends Class {
* Specification of "forbidden methods".
*/
// Forbidden container interference.
/** A method that may cause EJB container interference. */
class ForbiddenContainerInterferenceMethod extends Method {
ForbiddenContainerInterferenceMethod() {
this instanceof SystemExitMethod or
@@ -236,6 +274,10 @@ class ForbiddenContainerInterferenceMethod extends Method {
}
}
/**
* A method named `exit` declared in
* the class `java.lang.System`.
*/
class SystemExitMethod extends Method {
SystemExitMethod() {
this.hasName("exit") and
@@ -249,6 +291,10 @@ class SystemExitMethod extends Method {
}
}
/**
* A method named `exit` or `halt` declared in
* the class `java.lang.Runtime` or a subclass thereof.
*/
class RuntimeExitOrHaltMethod extends Method {
RuntimeExitOrHaltMethod() {
(this.hasName("exit") or this.hasName("halt")) and
@@ -262,6 +308,10 @@ class RuntimeExitOrHaltMethod extends Method {
}
}
/**
* A method named `addShutdownHook` or `removeShutdownHook` declared in
* the class `java.lang.Runtime` or a subclass thereof.
*/
class RuntimeAddOrRemoveShutdownHookMethod extends Method {
RuntimeAddOrRemoveShutdownHookMethod() {
(this.hasName("addShutdownHook") or this.hasName("removeShutdownHook")) and
@@ -275,6 +325,10 @@ class RuntimeAddOrRemoveShutdownHookMethod extends Method {
}
}
/**
* A method named `setErr` or `setOut` declared in
* the class `java.lang.System`.
*/
class SystemSetPrintStreamMethod extends Method {
SystemSetPrintStreamMethod() {
(this.hasName("setErr") or this.hasName("setOut")) and
@@ -288,6 +342,10 @@ class SystemSetPrintStreamMethod extends Method {
}
}
/**
* A method named `setIn` declared in
* the class `java.lang.System`.
*/
class SystemSetInputStreamMethod extends Method {
SystemSetInputStreamMethod() {
this.hasName("setIn") and
@@ -301,6 +359,10 @@ class SystemSetInputStreamMethod extends Method {
}
}
/**
* A method named `getSecurityManager` declared in
* the class `java.lang.System`.
*/
class SystemGetSecurityManagerMethod extends Method {
SystemGetSecurityManagerMethod() {
this.hasName("getSecurityManager") and
@@ -313,6 +375,10 @@ class SystemGetSecurityManagerMethod extends Method {
}
}
/**
* A method named `setSecurityManager` declared in
* the class `java.lang.System`.
*/
class SystemSetSecurityManagerMethod extends Method {
SystemSetSecurityManagerMethod() {
this.hasName("setSecurityManager") and
@@ -326,6 +392,10 @@ class SystemSetSecurityManagerMethod extends Method {
}
}
/**
* A method named `inheritedChannel` declared in
* the class `java.lang.System`.
*/
class SystemInheritedChannelMethod extends Method {
SystemInheritedChannelMethod() {
this.hasName("inheritedChannel") and
@@ -338,7 +408,7 @@ class SystemInheritedChannelMethod extends Method {
}
}
// Forbidden serialization.
/** A method involving serialization that may not be called from an EJB. */
class ForbiddenSerializationMethod extends Method {
ForbiddenSerializationMethod() {
this instanceof EnableReplaceObjectMethod or
@@ -350,6 +420,10 @@ class ForbiddenSerializationMethod extends Method {
}
}
/**
* A method named `enableReplaceObject` declared in
* the class `java.io.ObjectInputStream` or a subclass thereof.
*/
class EnableReplaceObjectMethod extends Method {
EnableReplaceObjectMethod() {
this.hasName("enableReplaceObject") and
@@ -363,6 +437,10 @@ class EnableReplaceObjectMethod extends Method {
}
}
/**
* A method named `replaceObject` declared in
* the class `java.io.ObjectInputStream` or a subclass thereof.
*/
class ReplaceObjectMethod extends Method {
ReplaceObjectMethod() {
this.hasName("replaceObject") and
@@ -376,6 +454,10 @@ class ReplaceObjectMethod extends Method {
}
}
/**
* A method named `enableResolveObject` declared in
* the class `java.io.ObjectInputStream` or a subclass thereof.
*/
class EnableResolveObjectMethod extends Method {
EnableResolveObjectMethod() {
this.hasName("enableResolveObject") and
@@ -389,6 +471,10 @@ class EnableResolveObjectMethod extends Method {
}
}
/**
* A method named `resolveObject` declared in
* the class `java.io.ObjectInputStream` or a subclass thereof.
*/
class ResolveObjectMethod extends Method {
ResolveObjectMethod() {
this.hasName("resolveObject") and
@@ -402,6 +488,10 @@ class ResolveObjectMethod extends Method {
}
}
/**
* A method named `resolveClass` declared in
* the class `java.io.ObjectInputStream` or a subclass thereof.
*/
class ResolveClassMethod extends Method {
ResolveClassMethod() {
this.hasName("resolveClass") and
@@ -415,6 +505,10 @@ class ResolveClassMethod extends Method {
}
}
/**
* A method named `resolveProxyClass` declared in
* the class `java.io.ObjectInputStream` or a subclass thereof.
*/
class ResolveProxyClassMethod extends Method {
ResolveProxyClassMethod() {
this.hasName("resolveProxyClass") and
@@ -434,7 +528,7 @@ class ResolveProxyClassMethod extends Method {
}
}
// Forbidden "set factory" methods.
/** A method involving network factory operations that may not be called from an EJB. */
class ForbiddenSetFactoryMethod extends Method {
ForbiddenSetFactoryMethod() {
this instanceof SetSocketFactoryMethod or
@@ -443,6 +537,10 @@ class ForbiddenSetFactoryMethod extends Method {
}
}
/**
* A method named `setSocketFactory` declared in
* the class `java.net.ServerSocket` or a subclass thereof.
*/
class SetSocketFactoryMethod extends Method {
SetSocketFactoryMethod() {
this.hasName("setSocketFactory") and
@@ -461,6 +559,10 @@ class SetSocketFactoryMethod extends Method {
}
}
/**
* A method named `setSocketImplFactory` declared in
* the class `java.net.Socket` or a subclass thereof.
*/
class SetSocketImplFactoryMethod extends Method {
SetSocketImplFactoryMethod() {
this.hasName("setSocketImplFactory") and
@@ -479,6 +581,10 @@ class SetSocketImplFactoryMethod extends Method {
}
}
/**
* A method named `setURLStreamHandlerFactory` declared in
* the class `java.net.URL` or a subclass thereof.
*/
class SetUrlStreamHandlerFactoryMethod extends Method {
SetUrlStreamHandlerFactoryMethod() {
this.hasName("setURLStreamHandlerFactory") and
@@ -497,7 +603,7 @@ class SetUrlStreamHandlerFactoryMethod extends Method {
}
}
// Forbidden native code methods.
/** A method involving native code that may not be called by an EJB. */
class ForbiddenNativeCodeMethod extends Method {
ForbiddenNativeCodeMethod() {
this instanceof SystemOrRuntimeLoadLibraryMethod or
@@ -505,6 +611,10 @@ class ForbiddenNativeCodeMethod extends Method {
}
}
/**
* A method named `load` or `loadLibrary` declared in the class
* `java.lang.System` or `java.lang.Runtime` or a subclass thereof.
*/
class SystemOrRuntimeLoadLibraryMethod extends Method {
SystemOrRuntimeLoadLibraryMethod() {
(this.hasName("load") or this.hasName("loadLibrary")) and
@@ -525,6 +635,10 @@ class SystemOrRuntimeLoadLibraryMethod extends Method {
}
}
/**
* A method named `exec` declared in the class
* `java.lang.Runtime` or in a subclass thereof.
*/
class RuntimeExecMethod extends Method {
RuntimeExecMethod() {
this.hasName("exec") and

View File

@@ -1,3 +1,5 @@
/** Provides classes and predicates for working with Java Server Faces annotations. */
import default
/**

View File

@@ -1,7 +1,10 @@
import java
import semmle.code.java.Maps
import SpringWeb
import SpringWebClient
/**
* An annotation type that identifies Spring components.
* An annotation type that identifies Spring controllers.
*/
class SpringControllerAnnotation extends AnnotationType {
SpringControllerAnnotation() {
@@ -13,6 +16,15 @@ class SpringControllerAnnotation extends AnnotationType {
}
}
/**
* An annotation type that identifies Spring rest controllers.
*
* Rest controllers are the same as controllers, but imply the `@ResponseBody` annotation.
*/
class SpringRestControllerAnnotation extends SpringControllerAnnotation {
SpringRestControllerAnnotation() { hasName("RestController") }
}
/**
* A class annotated, directly or indirectly, as a Spring `Controller`.
*/
@@ -20,6 +32,13 @@ class SpringController extends Class {
SpringController() { getAnAnnotation().getType() instanceof SpringControllerAnnotation }
}
/**
* A class annotated, directly or indirectly, as a Spring `RestController`.
*/
class SpringRestController extends SpringController {
SpringRestController() { getAnAnnotation().getType() instanceof SpringRestControllerAnnotation }
}
/**
* A method on a Spring controller which is accessed by the Spring MVC framework.
*/
@@ -38,7 +57,7 @@ class SpringModelAttributeMethod extends SpringControllerMethod {
// not declared with @Inherited.
exists(Method superMethod |
this.overrides*(superMethod) and
superMethod.hasAnnotation("org.springframework.web.bind.annotation", "ModelAttribute")
superMethod.getAnAnnotation() instanceof SpringModelAttributeAnnotation
)
}
}
@@ -58,19 +77,180 @@ class SpringInitBinderMethod extends SpringControllerMethod {
}
}
/**
* An `AnnotationType` that is used to indicate a `RequestMapping`.
*/
class SpringRequestMappingAnnotationType extends AnnotationType {
SpringRequestMappingAnnotationType() {
// `@RequestMapping` used directly as an annotation.
hasQualifiedName("org.springframework.web.bind.annotation", "RequestMapping")
or
// `@RequestMapping` can be used as a meta-annotation on other annotation types, e.g. GetMapping, PostMapping etc.
getAnAnnotation().getType() instanceof SpringRequestMappingAnnotationType
}
}
/**
* An `AnnotationType` that is used to indicate a `ResponseBody`.
*/
class SpringResponseBodyAnnotationType extends AnnotationType {
SpringResponseBodyAnnotationType() {
// `@ResponseBody` used directly as an annotation.
hasQualifiedName("org.springframework.web.bind.annotation", "ResponseBody")
}
}
/**
* A method on a Spring controller that is executed in response to a web request.
*/
class SpringRequestMappingMethod extends SpringControllerMethod {
Annotation requestMappingAnnotation;
SpringRequestMappingMethod() {
// Any method that declares the @RequestMapping annotation, or overrides a method that declares
// the annotation. We have to do this explicit check because the @RequestMapping annotation is
// not declared with @Inherited.
exists(Method superMethod |
this.overrides*(superMethod) and
superMethod.hasAnnotation("org.springframework.web.bind.annotation", "RequestMapping")
requestMappingAnnotation = superMethod.getAnAnnotation() and
requestMappingAnnotation.getType() instanceof SpringRequestMappingAnnotationType
)
}
/** Gets a request mapping parameter. */
SpringRequestMappingParameter getARequestParameter() { result = getAParameter() }
/** Gets the "produces" @RequestMapping annotation value, if present. */
string getProduces() {
result =
requestMappingAnnotation.getValue("produces").(CompileTimeConstantExpr).getStringValue()
}
/** Holds if this is considered an `@ResponseBody` method. */
predicate isResponseBody() {
getAnAnnotation().getType() instanceof SpringResponseBodyAnnotationType or
getDeclaringType().getAnAnnotation().getType() instanceof SpringResponseBodyAnnotationType or
getDeclaringType() instanceof SpringRestController
}
}
/** A Spring framework annotation indicating remote user input from servlets. */
class SpringServletInputAnnotation extends Annotation {
SpringServletInputAnnotation() {
exists(AnnotationType a |
a = this.getType() and
a.getPackage().getName() = "org.springframework.web.bind.annotation"
|
a.hasName("MatrixVariable") or
a.hasName("RequestParam") or
a.hasName("RequestHeader") or
a.hasName("CookieValue") or
a.hasName("RequestPart") or
a.hasName("PathVariable") or
a.hasName("RequestBody")
)
}
}
/** An annotation of the type `org.springframework.web.bind.annotation.ModelAttribute`. */
class SpringModelAttributeAnnotation extends Annotation {
SpringModelAttributeAnnotation() {
getType().hasQualifiedName("org.springframework.web.bind.annotation", "ModelAttribute")
}
}
/** A parameter of a `SpringRequestMappingMethod`. */
class SpringRequestMappingParameter extends Parameter {
SpringRequestMappingParameter() { getCallable() instanceof SpringRequestMappingMethod }
/** Holds if the parameter should not be consider a direct source of taint. */
predicate isNotDirectlyTaintedInput() {
getType().(RefType).getAnAncestor() instanceof SpringWebRequest or
getType().(RefType).getAnAncestor() instanceof SpringNativeWebRequest or
getType().(RefType).getAnAncestor().hasQualifiedName("javax.servlet", "ServletRequest") or
getType().(RefType).getAnAncestor().hasQualifiedName("javax.servlet", "ServletResponse") or
getType().(RefType).getAnAncestor().hasQualifiedName("javax.servlet.http", "HttpSession") or
getType().(RefType).getAnAncestor().hasQualifiedName("javax.servlet.http", "PushBuilder") or
getType().(RefType).getAnAncestor().hasQualifiedName("java.security", "Principal") or
getType().(RefType).getAnAncestor().hasQualifiedName("org.springframework.http", "HttpMethod") or
getType().(RefType).getAnAncestor().hasQualifiedName("java.util", "Locale") or
getType().(RefType).getAnAncestor().hasQualifiedName("java.util", "TimeZone") or
getType().(RefType).getAnAncestor().hasQualifiedName("java.time", "ZoneId") or
getType().(RefType).getAnAncestor().hasQualifiedName("java.io", "OutputStream") or
getType().(RefType).getAnAncestor().hasQualifiedName("java.io", "Writer") or
getType()
.(RefType)
.getAnAncestor()
.hasQualifiedName("org.springframework.web.servlet.mvc.support", "RedirectAttributes") or
// Also covers BindingResult. Note, you can access the field value through this interface, which should be considered tainted
getType().(RefType).getAnAncestor().hasQualifiedName("org.springframework.validation", "Errors") or
getType()
.(RefType)
.getAnAncestor()
.hasQualifiedName("org.springframework.web.bind.support", "SessionStatus") or
getType()
.(RefType)
.getAnAncestor()
.hasQualifiedName("org.springframework.web.util", "UriComponentsBuilder") or
getType()
.(RefType)
.getAnAncestor()
.hasQualifiedName("org.springframework.data.domain", "Pageable") or
this instanceof SpringModel
}
private predicate isExplicitlyTaintedInput() {
// InputStream or Reader parameters allow access to the body of a request
getType().(RefType).getAnAncestor().hasQualifiedName("java.io", "InputStream") or
getType().(RefType).getAnAncestor().hasQualifiedName("java.io", "Reader") or
// The SpringServletInputAnnotations allow access to the URI, request parameters, cookie values and the body of the request
this.getAnAnnotation() instanceof SpringServletInputAnnotation or
// HttpEntity is like @RequestBody, but with a wrapper including the headers
// TODO model unwrapping aspects
getType().(RefType).getASourceSupertype*() instanceof SpringHttpEntity or
this
.getAnAnnotation()
.getType()
.hasQualifiedName("org.springframework.web.bind.annotation", "RequestAttribute") or
this
.getAnAnnotation()
.getType()
.hasQualifiedName("org.springframework.web.bind.annotation", "SessionAttribute")
}
private predicate isImplicitRequestParam() {
// Any parameter which is not explicitly handled, is consider to be an `@RequestParam`, if
// it is a simple bean property
not isNotDirectlyTaintedInput() and
not isExplicitlyTaintedInput() and
(
getType() instanceof PrimitiveType or
getType() instanceof TypeString
)
}
private predicate isImplicitModelAttribute() {
// Any parameter which is not explicitly handled, is consider to be an `@ModelAttribute`, if
// it is not an implicit request param
not isNotDirectlyTaintedInput() and
not isExplicitlyTaintedInput() and
not isImplicitRequestParam()
}
/** Holds if this is an explicit or implicit `@ModelAttribute` parameter. */
predicate isModelAttribute() {
isImplicitModelAttribute() or
getAnAnnotation() instanceof SpringModelAttributeAnnotation
}
/** Holds if the input is tainted. */
predicate isTaintedInput() {
isExplicitlyTaintedInput()
or
// Any parameter which is not explicitly identified, is consider to be an `@RequestParam`, if
// it is a simple bean property) or a @ModelAttribute if not
not isNotDirectlyTaintedInput()
}
}
/**
@@ -90,7 +270,7 @@ abstract class SpringModel extends Parameter {
* A `java.util.Map` can be accepted as the model parameter for a Spring `RequestMapping` method.
*/
class SpringModelPlainMap extends SpringModel {
SpringModelPlainMap() { getType().(RefType).hasQualifiedName("java.util", "Map") }
SpringModelPlainMap() { getType() instanceof MapType }
override RefType getATypeInModel() {
exists(MethodAccess methodCall |
@@ -133,3 +313,39 @@ class SpringModelResponseType extends RefType {
exists(SpringModel model | usesType(model.getATypeInModel(), this))
}
}
/** Strips wrapper types. */
private RefType stripType(Type t) {
result = t or
result = stripType(t.(Array).getComponentType()) or
result = stripType(t.(ParameterizedType).getATypeArgument())
}
/**
* A user data type that may be populated from an HTTP request.
*
* This includes types directly referred to as either `@ModelAttribute` or `@RequestBody` parameters,
* or types that are referred to by those types.
*/
class SpringUntrustedDataType extends RefType {
SpringUntrustedDataType() {
exists(SpringRequestMappingParameter p |
p.isModelAttribute()
or
p.getAnAnnotation().(SpringServletInputAnnotation).getType().hasName("RequestBody")
|
this.fromSource() and
this = stripType(p.getType())
)
or
exists(SpringRestTemplateResponseEntityMethod rm |
this = stripType(rm.getAReference().getType().(ParameterizedType).getTypeArgument(0)) and
this.fromSource()
)
or
exists(SpringUntrustedDataType mt |
this = stripType(mt.getAField().getType()) and
this.fromSource()
)
}
}

View File

@@ -0,0 +1,40 @@
/**
* Provides classes for working with Spring classes and interfaces from
* `org.springframework.http`.
*/
import java
/** The class `org.springframework.http.HttpEntity` or an instantiation of it. */
class SpringHttpEntity extends Class {
SpringHttpEntity() {
this.getSourceDeclaration().hasQualifiedName("org.springframework.http", "HttpEntity")
}
}
/** The class `org.springframework.http.RequestEntity` or an instantiation of it. */
class SpringRequestEntity extends Class {
SpringRequestEntity() {
this.getSourceDeclaration().hasQualifiedName("org.springframework.http", "RequestEntity")
}
}
/** The class `org.springframework.http.ResponseEntity` or an instantiation of it. */
class SpringResponseEntity extends Class {
SpringResponseEntity() {
this.getSourceDeclaration().hasQualifiedName("org.springframework.http", "ResponseEntity")
}
}
/** The nested class `BodyBuilder` in `org.springframework.http.ResponseEntity`. */
class SpringResponseEntityBodyBuilder extends Interface {
SpringResponseEntityBodyBuilder() {
this.getSourceDeclaration().getEnclosingType() instanceof SpringResponseEntity and
this.hasName("BodyBuilder")
}
}
/** The class `org.springframework.http.HttpHeaders`. */
class SpringHttpHeaders extends Class {
SpringHttpHeaders() { this.hasQualifiedName("org.springframework.http", "HttpHeaders") }
}

View File

@@ -0,0 +1,19 @@
/**
* Provides classes for working with Spring web requests.
*/
import java
/** An interface for web requests in the Spring framework. */
class SpringWebRequest extends Class {
SpringWebRequest() {
this.hasQualifiedName("org.springframework.web.context.request", "WebRequest")
}
}
/** An interface for web requests in the Spring framework. */
class SpringNativeWebRequest extends Class {
SpringNativeWebRequest() {
this.hasQualifiedName("org.springframework.web.context.request", "NativeWebRequest")
}
}

View File

@@ -0,0 +1,29 @@
/**
* Provides classes for working with Spring web clients.
*/
import java
import SpringHttp
/** The class `org.springframework.web.client.RestTemplate`. */
class SpringRestTemplate extends Class {
SpringRestTemplate() { this.hasQualifiedName("org.springframework.web.client", "RestTemplate") }
}
/**
* A method declared in `org.springframework.web.client.RestTemplate` that
* returns a `SpringResponseEntity`.
*/
class SpringRestTemplateResponseEntityMethod extends Method {
SpringRestTemplateResponseEntityMethod() {
this.getDeclaringType() instanceof SpringRestTemplate and
this.getReturnType() instanceof SpringResponseEntity
}
}
/** The interface `org.springframework.web.reactive.function.client.WebClient`. */
class SpringWebClient extends Interface {
SpringWebClient() {
this.hasQualifiedName("org.springframework.web.reactive.function.client", "WebClient")
}
}

View File

@@ -1,3 +1,7 @@
/**
* Provides predicates and classes relating to encryption in Java.
*/
import java
class SSLClass extends RefType {
@@ -85,8 +89,10 @@ private string algorithmRegex(string algorithmString) {
"((^|.*[A-Z]{2}|.*[^a-zA-Z])(" + algorithmString.toLowerCase() + ")([^a-z].*|$))"
}
/** Gets a blacklist of algorithms that are known to be insecure. */
private string algorithmBlacklist() {
/**
* Gets the name of an algorithm that is known to be insecure.
*/
string getAnInsecureAlgorithmName() {
result = "DES" or
result = "RC2" or
result = "RC4" or
@@ -94,32 +100,40 @@ private string algorithmBlacklist() {
result = "ARCFOUR" // a variant of RC4
}
// These are only bad if they're being used for encryption.
private string hashAlgorithmBlacklist() {
/**
* Gets the name of a hash algorithm that is insecure if it is being used for
* encryption.
*/
string getAnInsecureHashAlgorithmName() {
result = "SHA1" or
result = "MD5"
}
private string rankedAlgorithmBlacklist(int i) {
private string rankedInsecureAlgorithm(int i) {
// In this case we know these are being used for encryption, so we want to match
// weak hash algorithms too.
result = rank[i](string s | s = algorithmBlacklist() or s = hashAlgorithmBlacklist())
}
private string algorithmBlacklistString(int i) {
i = 1 and result = rankedAlgorithmBlacklist(i)
or
result = rankedAlgorithmBlacklist(i) + "|" + algorithmBlacklistString(i - 1)
}
/** Gets a regex for matching strings that look like they contain a blacklisted algorithm. */
string algorithmBlacklistRegex() {
result =
algorithmRegex(algorithmBlacklistString(max(int i | exists(rankedAlgorithmBlacklist(i)))))
rank[i](string s | s = getAnInsecureAlgorithmName() or s = getAnInsecureHashAlgorithmName())
}
/** Gets a whitelist of algorithms that are known to be secure. */
private string algorithmWhitelist() {
private string insecureAlgorithmString(int i) {
i = 1 and result = rankedInsecureAlgorithm(i)
or
result = rankedInsecureAlgorithm(i) + "|" + insecureAlgorithmString(i - 1)
}
/**
* Gets the regular expression used for matching strings that look like they
* contain an algorithm that is known to be insecure.
*/
string getInsecureAlgorithmRegex() {
result = algorithmRegex(insecureAlgorithmString(max(int i | exists(rankedInsecureAlgorithm(i)))))
}
/**
* Gets the name of an algorithm that is known to be secure.
*/
string getASecureAlgorithmName() {
result = "RSA" or
result = "SHA256" or
result = "SHA512" or
@@ -130,20 +144,50 @@ private string algorithmWhitelist() {
result = "ECIES"
}
private string rankedAlgorithmWhitelist(int i) { result = rank[i](algorithmWhitelist()) }
private string rankedSecureAlgorithm(int i) { result = rank[i](getASecureAlgorithmName()) }
private string algorithmWhitelistString(int i) {
i = 1 and result = rankedAlgorithmWhitelist(i)
private string secureAlgorithmString(int i) {
i = 1 and result = rankedSecureAlgorithm(i)
or
result = rankedAlgorithmWhitelist(i) + "|" + algorithmWhitelistString(i - 1)
result = rankedSecureAlgorithm(i) + "|" + secureAlgorithmString(i - 1)
}
/** Gets a regex for matching strings that look like they contain a whitelisted algorithm. */
string algorithmWhitelistRegex() {
result =
algorithmRegex(algorithmWhitelistString(max(int i | exists(rankedAlgorithmWhitelist(i)))))
/**
* Gets a regular expression for matching strings that look like they
* contain an algorithm that is known to be secure.
*/
string getSecureAlgorithmRegex() {
result = algorithmRegex(secureAlgorithmString(max(int i | exists(rankedSecureAlgorithm(i)))))
}
/**
* DEPRECATED: Terminology has been updated. Use `getAnInsecureAlgorithmName()`
* instead.
*/
deprecated string algorithmBlacklist() { result = getAnInsecureAlgorithmName() }
/**
* DEPRECATED: Terminology has been updated. Use
* `getAnInsecureHashAlgorithmName()` instead.
*/
deprecated string hashAlgorithmBlacklist() { result = getAnInsecureHashAlgorithmName() }
/**
* DEPRECATED: Terminology has been updated. Use `getInsecureAlgorithmRegex()` instead.
*/
deprecated string algorithmBlacklistRegex() { result = getInsecureAlgorithmRegex() }
/**
* DEPRECATED: Terminology has been updated. Use `getASecureAlgorithmName()`
* instead.
*/
deprecated string algorithmWhitelist() { result = getASecureAlgorithmName() }
/**
* DEPRECATED: Terminology has been updated. Use `getSecureAlgorithmRegex()` instead.
*/
deprecated string algorithmWhitelistRegex() { result = getSecureAlgorithmRegex() }
/**
* Any use of a cryptographic element that specifies an encryption
* algorithm. For example, methods returning ciphers, decryption methods,
@@ -220,11 +264,16 @@ abstract class JavaSecurityAlgoSpec extends CryptoAlgoSpec { }
class JavaSecurityMessageDigest extends JavaSecurityAlgoSpec {
JavaSecurityMessageDigest() {
exists(Constructor c | c.getAReference() = this |
c.getDeclaringType().getQualifiedName() = "java.security.MessageDigest"
c.getDeclaringType().hasQualifiedName("java.security", "MessageDigest")
)
or
exists(Method m | m.getAReference() = this |
m.getDeclaringType().hasQualifiedName("java.security", "MessageDigest") and
m.getName() = "getInstance"
)
}
override Expr getAlgoSpec() { result = this.(ConstructorCall).getArgument(0) }
override Expr getAlgoSpec() { result = this.(Call).getArgument(0) }
}
class JavaSecuritySignature extends JavaSecurityAlgoSpec {

View File

@@ -0,0 +1,146 @@
/**
* Definitions for reasoning about untrusted data used in APIs defined outside the
* database.
*/
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.TaintTracking
/**
* A `Method` that is considered a "safe" external API from a security perspective.
*/
abstract class SafeExternalAPIMethod extends Method { }
/** The default set of "safe" external APIs. */
private class DefaultSafeExternalAPIMethod extends SafeExternalAPIMethod {
DefaultSafeExternalAPIMethod() {
this instanceof EqualsMethod
or
getName().regexpMatch("size|length|compareTo|getClass|lastIndexOf")
or
this.getDeclaringType().hasQualifiedName("org.apache.commons.lang3", "Validate")
or
getQualifiedName() = "Objects.equals"
or
getDeclaringType() instanceof TypeString and getName() = "equals"
or
getDeclaringType().hasQualifiedName("com.google.common.base", "Preconditions")
or
getDeclaringType().getPackage().getName().matches("org.junit%")
or
getDeclaringType().hasQualifiedName("com.google.common.base", "Strings") and
getName() = "isNullOrEmpty"
or
getDeclaringType().hasQualifiedName("org.apache.commons.lang3", "StringUtils") and
getName() = "isNotEmpty"
or
getDeclaringType().hasQualifiedName("java.lang", "Character") and
getName() = "isDigit"
or
getDeclaringType().hasQualifiedName("java.lang", "String") and
getName().regexpMatch("equalsIgnoreCase|regionMatches")
or
getDeclaringType().hasQualifiedName("java.lang", "Boolean") and
getName() = "parseBoolean"
or
getDeclaringType().hasQualifiedName("org.apache.commons.io", "IOUtils") and
getName() = "closeQuietly"
or
getDeclaringType().hasQualifiedName("org.springframework.util", "StringUtils") and
getName().regexpMatch("hasText|isEmpty")
}
}
/** A node representing data being passed to an external API. */
class ExternalAPIDataNode extends DataFlow::Node {
Call call;
int i;
ExternalAPIDataNode() {
(
// Argument to call to a method
this.asExpr() = call.getArgument(i)
or
// Qualifier to call to a method which returns non trivial value
this.asExpr() = call.getQualifier() and
i = -1 and
not call.getCallee().getReturnType() instanceof VoidType and
not call.getCallee().getReturnType() instanceof BooleanType
) and
// Defined outside the source archive
not call.getCallee().fromSource() and
// Not a call to an method which is overridden in source
not exists(Method m |
m.getASourceOverriddenMethod() = call.getCallee().getSourceDeclaration() and
m.fromSource()
) and
// Not already modeled as a taint step
not exists(DataFlow::Node next | TaintTracking::localTaintStep(this, next)) and
// Not a call to a known safe external API
not call.getCallee() instanceof SafeExternalAPIMethod
}
/** Gets the called API `Method`. */
Method getMethod() { result = call.getCallee() }
/** Gets the index which is passed untrusted data (where -1 indicates the qualifier). */
int getIndex() { result = i }
/** Gets the description of the method being called. */
string getMethodDescription() {
result = getMethod().getDeclaringType().getPackage() + "." + getMethod().getQualifiedName()
}
}
/** A configuration for tracking flow from `RemoteFlowSource`s to `ExternalAPIDataNode`s. */
class UntrustedDataToExternalAPIConfig extends TaintTracking::Configuration {
UntrustedDataToExternalAPIConfig() { this = "UntrustedDataToExternalAPIConfig" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) { sink instanceof ExternalAPIDataNode }
}
/** A node representing untrusted data being passed to an external API. */
class UntrustedExternalAPIDataNode extends ExternalAPIDataNode {
UntrustedExternalAPIDataNode() { any(UntrustedDataToExternalAPIConfig c).hasFlow(_, this) }
/** Gets a source of untrusted data which is passed to this external API data node. */
DataFlow::Node getAnUntrustedSource() {
any(UntrustedDataToExternalAPIConfig c).hasFlow(result, this)
}
}
private newtype TExternalAPI =
TExternalAPIParameter(Method m, int index) {
exists(UntrustedExternalAPIDataNode n |
m = n.getMethod() and
index = n.getIndex()
)
}
/** An external API which is used with untrusted data. */
class ExternalAPIUsedWithUntrustedData extends TExternalAPI {
/** Gets a possibly untrusted use of this external API. */
UntrustedExternalAPIDataNode getUntrustedDataNode() {
this = TExternalAPIParameter(result.getMethod(), result.getIndex())
}
/** Gets the number of untrusted sources used with this external API. */
int getNumberOfUntrustedSources() {
result = count(getUntrustedDataNode().getAnUntrustedSource())
}
/** Gets a textual representation of this element. */
string toString() {
exists(Method m, int index, string indexString |
if index = -1 then indexString = "qualifier" else indexString = "param " + index
|
this = TExternalAPIParameter(m, index) and
result =
m.getDeclaringType().(RefType).getQualifiedName() + "." + m.getSignature() + " [" +
indexString + "]"
)
}
}

View File

@@ -9,9 +9,9 @@ private predicate fileRead(VarAccess fileAccess, Expr fileReadingExpr) {
cie = fileReadingExpr and
cie.getArgument(0) = fileAccess
|
cie.getConstructedType().hasQualifiedName("java.io", "RandomAccessFile") or
cie.getConstructedType().hasQualifiedName("java.io", "FileReader") or
cie.getConstructedType().hasQualifiedName("java.io", "FileInputStream")
cie
.getConstructedType()
.hasQualifiedName("java.io", ["RandomAccessFile", "FileReader", "FileInputStream"])
)
or
exists(MethodAccess ma, Method filesMethod |
@@ -22,13 +22,9 @@ private predicate fileRead(VarAccess fileAccess, Expr fileReadingExpr) {
// represented by the first argument.
filesMethod.getDeclaringType().hasQualifiedName("java.nio.file", "Files") and
fileAccess = ma.getArgument(0) and
(
filesMethod.hasName("readAllBytes") or
filesMethod.hasName("readAllLines") or
filesMethod.hasName("newBufferedReader") or
filesMethod.hasName("newInputReader") or
filesMethod.hasName("newByteChannel")
)
filesMethod
.hasName(["readAllBytes", "readAllLines", "readString", "lines", "newBufferedReader",
"newInputStream", "newByteChannel"])
)
)
or

View File

@@ -0,0 +1,413 @@
/** Provides classes to reason about LDAP injection attacks. */
import java
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.frameworks.Jndi
import semmle.code.java.frameworks.UnboundId
import semmle.code.java.frameworks.SpringLdap
import semmle.code.java.frameworks.ApacheLdap
/** A data flow sink for unvalidated user input that is used to construct LDAP queries. */
abstract class LdapInjectionSink extends DataFlow::Node { }
/** A sanitizer that prevents LDAP injection attacks. */
abstract class LdapInjectionSanitizer extends DataFlow::Node { }
/**
* A unit class for adding additional taint steps.
*
* Extend this class to add additional taint steps that should apply to the `LdapInjectionFlowConfig`.
*/
class LdapInjectionAdditionalTaintStep extends TaintTracking::Unit {
/**
* Holds if the step from `node1` to `node2` should be considered a taint
* step for the `LdapInjectionFlowConfig` configuration.
*/
abstract predicate step(DataFlow::Node node1, DataFlow::Node node2);
}
/** Default sink for LDAP injection vulnerabilities. */
private class DefaultLdapInjectionSink extends LdapInjectionSink {
DefaultLdapInjectionSink() {
exists(MethodAccess ma, Method m, int index |
ma.getMethod() = m and
ma.getArgument(index) = this.asExpr() and
ldapInjectionSinkMethod(m, index)
)
}
}
/** Holds if the method parameter at `index` is susceptible to an LDAP injection attack. */
private predicate ldapInjectionSinkMethod(Method m, int index) {
jndiLdapInjectionSinkMethod(m, index) or
unboundIdLdapInjectionSinkMethod(m, index) or
springLdapInjectionSinkMethod(m, index) or
apacheLdapInjectionSinkMethod(m, index)
}
/** Holds if the JNDI method parameter at `index` is susceptible to an LDAP injection attack. */
private predicate jndiLdapInjectionSinkMethod(Method m, int index) {
m.getDeclaringType().getAnAncestor() instanceof TypeDirContext and
m.hasName("search") and
index in [0 .. 1]
}
/** Holds if the UnboundID method parameter at `index` is susceptible to an LDAP injection attack. */
private predicate unboundIdLdapInjectionSinkMethod(Method m, int index) {
exists(Parameter param | m.getParameter(index) = param and not param.isVarargs() |
m instanceof MethodUnboundIdLDAPConnectionSearch or
m instanceof MethodUnboundIdLDAPConnectionAsyncSearch or
m instanceof MethodUnboundIdLDAPConnectionSearchForEntry
)
}
/** Holds if the Spring method parameter at `index` is susceptible to an LDAP injection attack. */
private predicate springLdapInjectionSinkMethod(Method m, int index) {
// LdapTemplate.authenticate, LdapTemplate.find* or LdapTemplate.search* method
(
m instanceof MethodSpringLdapTemplateAuthenticate or
m instanceof MethodSpringLdapTemplateFind or
m instanceof MethodSpringLdapTemplateFindOne or
m instanceof MethodSpringLdapTemplateSearch or
m instanceof MethodSpringLdapTemplateSearchForContext or
m instanceof MethodSpringLdapTemplateSearchForObject
) and
(
// Parameter index is 1 (DN or query) or 2 (filter) if method is not authenticate
index in [0 .. 1] and
not m instanceof MethodSpringLdapTemplateAuthenticate
or
// But it's not the last parameter in case of authenticate method (last param is password)
index in [0 .. 1] and
index < m.getNumberOfParameters() - 1 and
m instanceof MethodSpringLdapTemplateAuthenticate
)
}
/** Holds if the Apache LDAP API method parameter at `index` is susceptible to an LDAP injection attack. */
private predicate apacheLdapInjectionSinkMethod(Method m, int index) {
exists(Parameter param | m.getParameter(index) = param and not param.isVarargs() |
m.getDeclaringType().getAnAncestor() instanceof TypeApacheLdapConnection and
m.hasName("search")
)
}
/** A sanitizer that clears the taint on (boxed) primitive types. */
private class DefaultLdapSanitizer extends LdapInjectionSanitizer {
DefaultLdapSanitizer() {
this.getType() instanceof PrimitiveType or
this.getType() instanceof BoxedType
}
}
/**
* Holds if `n1` to `n2` is a dataflow step that converts between `String` and `LdapName`,
* i.e. `new LdapName(tainted)`.
*/
private predicate ldapNameStep(DataFlow::ExprNode n1, DataFlow::ExprNode n2) {
exists(ConstructorCall cc | cc.getConstructedType() instanceof TypeLdapName |
n1.asExpr() = cc.getAnArgument() and
n2.asExpr() = cc
)
}
/**
* Holds if `n1` to `n2` is a dataflow step that converts between `List<Rdn>` and `LdapName`,
* i.e. `new LdapName().addAll(tainted)`.
*/
private predicate ldapNameAddAllStep(DataFlow::ExprNode n1, DataFlow::ExprNode n2) {
exists(MethodAccess ma |
n1.asExpr() = ma.getAnArgument() and
(n2.asExpr() = ma or n2.asExpr() = ma.getQualifier())
|
ma.getMethod() instanceof MethodLdapNameAddAll
)
}
/**
* Holds if `n1` to `n2` is a dataflow step that converts between `LdapName` and `LdapName` or
* `String`, i.e. `taintedLdapName.clone()`, `taintedLdapName.getAll()`,
* `taintedLdapName.getRdns()` or `taintedLdapName.toString()`.
*/
private predicate ldapNameGetCloneStep(DataFlow::ExprNode n1, DataFlow::ExprNode n2) {
exists(MethodAccess ma, Method m |
n1.asExpr() = ma.getQualifier() and
n2.asExpr() = ma and
ma.getMethod() = m
|
m instanceof MethodLdapNameClone or
m instanceof MethodLdapNameGetAll or
m instanceof MethodLdapNameGetRdns or
m instanceof MethodLdapNameToString
)
}
/**
* Holds if `n1` to `n2` is a dataflow step that converts between `String` and UnboundID `Filter`,
* i.e. `Filter.create*(tainted)`.
*/
private predicate filterStep(DataFlow::ExprNode n1, DataFlow::ExprNode n2) {
exists(MethodAccess ma, Method m |
n1.asExpr() = ma.getAnArgument() and
n2.asExpr() = ma and
ma.getMethod() = m
|
m instanceof MethodUnboundIdFilterCreate or
m instanceof MethodUnboundIdFilterCreateANDFilter or
m instanceof MethodUnboundIdFilterCreateNOTFilter or
m instanceof MethodUnboundIdFilterCreateORFilter or
m instanceof MethodUnboundIdFilterSimplifyFilter
)
}
/**
* Holds if `n1` to `n2` is a dataflow step that converts between UnboundID `Filter` and `String`,
* i.e. `taintedFilter.toString()` or `taintedFilter.toString(buffer)`.
*/
private predicate filterToStringStep(DataFlow::ExprNode n1, DataFlow::ExprNode n2) {
exists(MethodAccess ma, Method m |
n1.asExpr() = ma.getQualifier() and
(n2.asExpr() = ma or n2.asExpr() = ma.getAnArgument())
|
ma.getMethod() = m and
m.getDeclaringType() instanceof TypeUnboundIdLdapFilter and
(m.hasName("toString") or m.hasName("toNormalizedString"))
)
}
/**
* Holds if `n1` to `n2` is a dataflow step that converts between `String` and UnboundID
* `SearchRequest`, i.e. `new SearchRequest(tainted)`.
*/
private predicate unboundIdSearchRequestStep(DataFlow::ExprNode n1, DataFlow::ExprNode n2) {
exists(ConstructorCall cc, int index, Parameter param |
cc.getConstructedType() instanceof TypeUnboundIdSearchRequest
|
n1.asExpr() = cc.getArgument(index) and
n2.asExpr() = cc and
cc.getConstructor().getParameter(index) = param and
not param.isVarargs()
)
}
/**
* Holds if `n1` to `n2` is a dataflow step that converts between UnboundID `SearchRequest`
* and UnboundID `SearchRequest`, i.e. `taintedSearchRequest.duplicate()`.
*/
private predicate unboundIdSearchRequestDuplicateStep(DataFlow::ExprNode n1, DataFlow::ExprNode n2) {
exists(MethodAccess ma, Method m | n1.asExpr() = ma.getQualifier() and n2.asExpr() = ma |
ma.getMethod() = m and
m.getDeclaringType().getAnAncestor() instanceof TypeUnboundIdReadOnlySearchRequest and
m.hasName("duplicate")
)
}
/**
* Holds if `n1` to `n2` is a dataflow step that converts between DN or filter and UnboundID
* `SearchRequest`, i.e. `searchRequest.setBaseDN(tainted)` or `searchRequest.setFilter(tainted)`.
*/
private predicate unboundIdSearchRequestSetStep(DataFlow::ExprNode n1, DataFlow::ExprNode n2) {
exists(MethodAccess ma, Method m |
n1.asExpr() = ma.getAnArgument() and
n2.asExpr() = ma.getQualifier() and
ma.getMethod() = m
|
m instanceof MethodUnboundIdSearchRequestSetBaseDN or
m instanceof MethodUnboundIdSearchRequestSetFilter
)
}
/**
* Holds if `n1` to `n2` is a dataflow step that converts between `String` and Spring `LdapQuery`,
* i.e. `LdapQueryBuilder.query().filter(tainted)` or `LdapQueryBuilder.query().base(tainted)`.
*/
private predicate ldapQueryStep(DataFlow::ExprNode n1, DataFlow::ExprNode n2) {
exists(MethodAccess ma, Method m, int index |
n1.asExpr() = ma.getArgument(index) and
n2.asExpr() = ma and
ma.getMethod() = m and
index = 0
|
m instanceof MethodSpringLdapQueryBuilderFilter or
m instanceof MethodSpringLdapQueryBuilderBase
)
}
/**
* Holds if `n1` to `n2` is a dataflow step that converts between Spring `LdapQueryBuilder` and
* `Name`, i.e. `taintedLdapQueryBuilder.base()`.
*/
private predicate ldapQueryBaseStep(DataFlow::ExprNode n1, DataFlow::ExprNode n2) {
exists(MethodAccess ma, Method m |
n1.asExpr() = ma.getQualifier() and
n2.asExpr() = ma and
ma.getMethod() = m
|
m instanceof MethodSpringLdapQueryBuilderBase and
m.getNumberOfParameters() = 0
)
}
/**
* Holds if `n1` to `n2` is a dataflow step that converts between Spring `LdapQueryBuilder`,
* `ConditionCriteria` or `ContainerCriteria`, i.e. when the query is built, for example
* `query().base(tainted).where("objectclass").is("person")`.
*/
private predicate ldapQueryBuilderStep(DataFlow::ExprNode n1, DataFlow::ExprNode n2) {
exists(MethodAccess ma, Method m |
n1.asExpr() = ma.getQualifier() and
n2.asExpr() = ma and
ma.getMethod() = m
|
(
m.getDeclaringType() instanceof TypeSpringLdapQueryBuilder or
m.getDeclaringType() instanceof TypeSpringConditionCriteria or
m.getDeclaringType() instanceof TypeSpringContainerCriteria
) and
(
m.getReturnType() instanceof TypeSpringLdapQueryBuilder or
m.getReturnType() instanceof TypeSpringConditionCriteria or
m.getReturnType() instanceof TypeSpringContainerCriteria
)
)
}
/**
* Holds if `n1` to `n2` is a dataflow step that converts between `String` and Spring
* `HardcodedFilter`, i.e. `new HardcodedFilter(tainted)`.
*/
private predicate hardcodedFilterStep(DataFlow::ExprNode n1, DataFlow::ExprNode n2) {
exists(ConstructorCall cc | cc.getConstructedType() instanceof TypeSpringHardcodedFilter |
n1.asExpr() = cc.getAnArgument() and
n2.asExpr() = cc
)
}
/**
* Holds if `n1` to `n2` is a dataflow step that converts between Spring `Filter` and
* `String`, i.e. `taintedFilter.toString()`, `taintedFilter.encode()` or
* `taintedFilter.encode(buffer)`.
*/
private predicate springLdapFilterToStringStep(DataFlow::ExprNode n1, DataFlow::ExprNode n2) {
exists(MethodAccess ma, Method m |
n1.asExpr() = ma.getQualifier() and
(n2.asExpr() = ma or n2.asExpr() = ma.getAnArgument()) and
ma.getMethod() = m
|
m.getDeclaringType().getAnAncestor() instanceof TypeSpringLdapFilter and
(m.hasName("encode") or m.hasName("toString"))
)
}
/**
* Holds if `n1` to `n2` is a dataflow step that converts between `String` and Spring
* `LdapNameBuilder`, i.e. `LdapNameBuilder.newInstance(tainted)` or
* `LdapNameBuilder.newInstance().add(tainted)`.
*/
private predicate ldapNameBuilderStep(DataFlow::ExprNode n1, DataFlow::ExprNode n2) {
exists(MethodAccess ma, Method m |
n1.asExpr() = ma.getAnArgument() and
(n2.asExpr() = ma or n2.asExpr() = ma.getQualifier()) and
ma.getMethod() = m and
m.getNumberOfParameters() = 1
|
m instanceof MethodSpringLdapNameBuilderNewInstance or
m instanceof MethodSpringLdapNameBuilderAdd
)
}
/**
* Holds if `n1` to `n2` is a dataflow step that converts between tainted Spring `LdapNameBuilder`
* and `LdapName`, `LdapNameBuilder.build()`.
*/
private predicate ldapNameBuilderBuildStep(DataFlow::ExprNode n1, DataFlow::ExprNode n2) {
exists(MethodAccess ma | n1.asExpr() = ma.getQualifier() and n2.asExpr() = ma |
ma.getMethod() instanceof MethodSpringLdapNameBuilderBuild
)
}
/**
* Holds if `n1` to `n2` is a dataflow step that converts between `String` and `LdapName` via
* Spring `LdapUtils.newLdapName`, i.e. `LdapUtils.newLdapName(tainted)`.
*/
private predicate ldapUtilsStep(DataFlow::ExprNode n1, DataFlow::ExprNode n2) {
exists(MethodAccess ma | n1.asExpr() = ma.getAnArgument() and n2.asExpr() = ma |
ma.getMethod() instanceof MethodSpringLdapUtilsNewLdapName
)
}
/**
* Holds if `n1` to `n2` is a dataflow step that converts between `String` and Apache LDAP API
* `SearchRequest`, i.e. `searchRequest.setFilter(tainted)` or `searchRequest.setBase(tainted)`.
*/
private predicate apacheSearchRequestStep(DataFlow::ExprNode n1, DataFlow::ExprNode n2) {
exists(MethodAccess ma, Method m |
n1.asExpr() = ma.getAnArgument() and
n2.asExpr() = ma.getQualifier()
|
ma.getMethod() = m and
m.getDeclaringType().getAnAncestor() instanceof TypeApacheSearchRequest and
(m.hasName("setFilter") or m.hasName("setBase"))
)
}
/**
* Holds if `n1` to `n2` is a dataflow step that converts between Apache LDAP API `SearchRequest`
* and filter or DN i.e. `tainterSearchRequest.getFilter()` or `taintedSearchRequest.getBase()`.
*/
private predicate apacheSearchRequestGetStep(DataFlow::ExprNode n1, DataFlow::ExprNode n2) {
exists(MethodAccess ma, Method m | n1.asExpr() = ma.getQualifier() and n2.asExpr() = ma |
ma.getMethod() = m and
m.getDeclaringType().getAnAncestor() instanceof TypeApacheSearchRequest and
(m.hasName("getFilter") or m.hasName("getBase"))
)
}
/**
* Holds if `n1` to `n2` is a dataflow step that converts between `String` and Apache LDAP API
* `Dn`, i.e. `new Dn(tainted)`.
*/
private predicate apacheLdapDnStep(DataFlow::ExprNode n1, DataFlow::ExprNode n2) {
exists(ConstructorCall cc | cc.getConstructedType() instanceof TypeApacheDn |
n1.asExpr() = cc.getAnArgument() and
n2.asExpr() = cc
)
}
/**
* Holds if `n1` to `n2` is a dataflow step that converts between Apache LDAP API `Dn`
* and `String` i.e. `taintedDn.getName()`, `taintedDn.getNormName()` or `taintedDn.toString()`.
*/
private predicate apacheLdapDnGetStep(DataFlow::ExprNode n1, DataFlow::ExprNode n2) {
exists(MethodAccess ma, Method m | n1.asExpr() = ma.getQualifier() and n2.asExpr() = ma |
ma.getMethod() = m and
m.getDeclaringType().getAnAncestor() instanceof TypeApacheDn and
(m.hasName("getName") or m.hasName("getNormName") or m.hasName("toString"))
)
}
/** A set of additional taint steps to consider when taint tracking LDAP related data flows. */
private class DefaultLdapInjectionAdditionalTaintStep extends LdapInjectionAdditionalTaintStep {
override predicate step(DataFlow::Node node1, DataFlow::Node node2) {
ldapNameStep(node1, node2) or
ldapNameAddAllStep(node1, node2) or
ldapNameGetCloneStep(node1, node2) or
filterStep(node1, node2) or
filterToStringStep(node1, node2) or
unboundIdSearchRequestStep(node1, node2) or
unboundIdSearchRequestDuplicateStep(node1, node2) or
unboundIdSearchRequestSetStep(node1, node2) or
ldapQueryStep(node1, node2) or
ldapQueryBaseStep(node1, node2) or
ldapQueryBuilderStep(node1, node2) or
hardcodedFilterStep(node1, node2) or
springLdapFilterToStringStep(node1, node2) or
ldapNameBuilderStep(node1, node2) or
ldapNameBuilderBuildStep(node1, node2) or
ldapUtilsStep(node1, node2) or
apacheSearchRequestStep(node1, node2) or
apacheSearchRequestGetStep(node1, node2) or
apacheLdapDnStep(node1, node2) or
apacheLdapDnGetStep(node1, node2)
}
}

View File

@@ -0,0 +1,141 @@
/**
* Models the different ways to create paths. Either by using `java.io.File`-related APIs or `java.nio.file.Path`-related APIs.
*/
import java
/** Models the creation of a path. */
abstract class PathCreation extends Expr {
/**
* Gets an input that is used in the creation of this path.
* This excludes inputs of type `File` and `Path`.
*/
abstract Expr getAnInput();
}
/** Models the `java.nio.file.Paths.get` method. */
private class PathsGet extends PathCreation, MethodAccess {
PathsGet() {
exists(Method m | m = this.getMethod() |
m.getDeclaringType() instanceof TypePaths and
m.getName() = "get"
)
}
override Expr getAnInput() { result = this.getAnArgument() }
}
/** Models the `java.nio.file.FileSystem.getPath` method. */
private class FileSystemGetPath extends PathCreation, MethodAccess {
FileSystemGetPath() {
exists(Method m | m = this.getMethod() |
m.getDeclaringType() instanceof TypeFileSystem and
m.getName() = "getPath"
)
}
override Expr getAnInput() { result = this.getAnArgument() }
}
/** Models the `new java.io.File(...)` constructor. */
private class FileCreation extends PathCreation, ClassInstanceExpr {
FileCreation() { this.getConstructedType() instanceof TypeFile }
override Expr getAnInput() {
result = this.getAnArgument() and
// Relevant arguments include those that are not a `File`.
not result.getType() instanceof TypeFile
}
}
/** Models the `java.nio.file.Path.resolveSibling` method. */
private class PathResolveSiblingCreation extends PathCreation, MethodAccess {
PathResolveSiblingCreation() {
exists(Method m | m = this.getMethod() |
m.getDeclaringType() instanceof TypePath and
m.getName() = "resolveSibling"
)
}
override Expr getAnInput() {
result = this.getAnArgument() and
// Relevant arguments are those of type `String`.
result.getType() instanceof TypeString
}
}
/** Models the `java.nio.file.Path.resolve` method. */
private class PathResolveCreation extends PathCreation, MethodAccess {
PathResolveCreation() {
exists(Method m | m = this.getMethod() |
m.getDeclaringType() instanceof TypePath and
m.getName() = "resolve"
)
}
override Expr getAnInput() {
result = this.getAnArgument() and
// Relevant arguments are those of type `String`.
result.getType() instanceof TypeString
}
}
/** Models the `java.nio.file.Path.of` method. */
private class PathOfCreation extends PathCreation, MethodAccess {
PathOfCreation() {
exists(Method m | m = this.getMethod() |
m.getDeclaringType() instanceof TypePath and
m.getName() = "of"
)
}
override Expr getAnInput() { result = this.getAnArgument() }
}
/** Models the `new java.io.FileWriter(...)` constructor. */
private class FileWriterCreation extends PathCreation, ClassInstanceExpr {
FileWriterCreation() { this.getConstructedType().hasQualifiedName("java.io", "FileWriter") }
override Expr getAnInput() {
result = this.getAnArgument() and
// Relevant arguments are those of type `String`.
result.getType() instanceof TypeString
}
}
/** Models the `new java.io.FileReader(...)` constructor. */
private class FileReaderCreation extends PathCreation, ClassInstanceExpr {
FileReaderCreation() { this.getConstructedType().hasQualifiedName("java.io", "FileReader") }
override Expr getAnInput() {
result = this.getAnArgument() and
// Relevant arguments are those of type `String`.
result.getType() instanceof TypeString
}
}
/** Models the `new java.io.FileInputStream(...)` constructor. */
private class FileInputStreamCreation extends PathCreation, ClassInstanceExpr {
FileInputStreamCreation() {
this.getConstructedType().hasQualifiedName("java.io", "FileInputStream")
}
override Expr getAnInput() {
result = this.getAnArgument() and
// Relevant arguments are those of type `String`.
result.getType() instanceof TypeString
}
}
/** Models the `new java.io.FileOutputStream(...)` constructor. */
private class FileOutputStreamCreation extends PathCreation, ClassInstanceExpr {
FileOutputStreamCreation() {
this.getConstructedType().hasQualifiedName("java.io", "FileOutputStream")
}
override Expr getAnInput() {
result = this.getAnArgument() and
// Relevant arguments are those of type `String`.
result.getType() instanceof TypeString
}
}

View File

@@ -0,0 +1,51 @@
/** Provides classes to reason about database query language injection vulnerabilities. */
import java
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.frameworks.Jdbc
import semmle.code.java.frameworks.jOOQ
import semmle.code.java.frameworks.android.SQLite
import semmle.code.java.frameworks.javaee.Persistence
import semmle.code.java.frameworks.SpringJdbc
import semmle.code.java.frameworks.MyBatis
import semmle.code.java.frameworks.Hibernate
/** A sink for database query language injection vulnerabilities. */
abstract class QueryInjectionSink extends DataFlow::Node { }
/** A sink for SQL injection vulnerabilities. */
private class SqlInjectionSink extends QueryInjectionSink {
SqlInjectionSink() {
this.asExpr() instanceof SqlExpr
or
exists(MethodAccess ma, Method m, int index |
ma.getMethod() = m and
ma.getArgument(index) = this.asExpr()
|
index = m.(SQLiteRunner).sqlIndex()
or
m instanceof BatchUpdateVarargsMethod
or
index = 0 and jdbcSqlMethod(m)
or
index = 0 and mybatisSqlMethod(m)
or
index = 0 and hibernateSqlMethod(m)
or
index = 0 and jOOQSqlMethod(m)
)
}
}
/** A sink for Java Persistence Query Language injection vulnerabilities. */
private class PersistenceQueryInjectionSink extends QueryInjectionSink {
PersistenceQueryInjectionSink() {
// the query (first) argument to a `createQuery` or `createNativeQuery` method on `EntityManager`
exists(MethodAccess call, TypeEntityManager em | call.getArgument(0) = this.asExpr() |
call.getMethod() = em.getACreateQueryMethod() or
call.getMethod() = em.getACreateNativeQueryMethod()
// note: `createNamedQuery` is safe, as it takes only the query name,
// and named queries can only be constructed using constants as the query text
)
}
}

View File

@@ -0,0 +1,49 @@
/** Provides classes to reason about header splitting attacks. */
import java
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.frameworks.Servlets
import semmle.code.java.frameworks.JaxWS
/** A sink that is vulnerable to an HTTP header splitting attack. */
abstract class HeaderSplittingSink extends DataFlow::Node { }
/** A source that introduces data considered safe to use by a header splitting source. */
abstract class SafeHeaderSplittingSource extends DataFlow::Node {
SafeHeaderSplittingSource() { this instanceof RemoteFlowSource }
}
/** A sink that identifies a Java Servlet or JaxWs method that is vulnerable to an HTTP header splitting attack. */
private class ServletHeaderSplittingSink extends HeaderSplittingSink {
ServletHeaderSplittingSink() {
exists(ResponseAddCookieMethod m, MethodAccess ma |
ma.getMethod() = m and
this.asExpr() = ma.getArgument(0)
)
or
exists(ResponseAddHeaderMethod m, MethodAccess ma |
ma.getMethod() = m and
this.asExpr() = ma.getAnArgument()
)
or
exists(ResponseSetHeaderMethod m, MethodAccess ma |
ma.getMethod() = m and
this.asExpr() = ma.getAnArgument()
)
or
exists(JaxRsResponseBuilder builder, Method m |
m = builder.getAMethod() and m.getName() = "header"
|
this.asExpr() = m.getAReference().getArgument(1)
)
}
}
/** A default source that introduces data considered safe to use by a header splitting source. */
private class DefaultSafeHeaderSplittingSource extends SafeHeaderSplittingSource {
DefaultSafeHeaderSplittingSource() {
this.asExpr().(MethodAccess).getMethod() instanceof HttpServletRequestGetHeaderMethod or
this.asExpr().(MethodAccess).getMethod() instanceof CookieGetNameMethod
}
}

View File

@@ -51,7 +51,14 @@ class SafeKryo extends DataFlow2::Configuration {
predicate unsafeDeserialization(MethodAccess ma, Expr sink) {
exists(Method m | m = ma.getMethod() |
m instanceof ObjectInputStreamReadObjectMethod and
sink = ma.getQualifier()
sink = ma.getQualifier() and
not exists(DataFlow::ExprNode node |
node.getExpr() = sink and
node
.getTypeBound()
.(RefType)
.hasQualifiedName("org.apache.commons.io.serialization", "ValidatingObjectInputStream")
)
or
m instanceof XMLDecoderReadObjectMethod and
sink = ma.getQualifier()

View File

@@ -0,0 +1,26 @@
/** Provides classes to reason about URL redirect attacks. */
import java
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.frameworks.Servlets
/** A URL redirection sink */
abstract class UrlRedirectSink extends DataFlow::Node { }
/** A Servlet URL redirection sink. */
private class ServletUrlRedirectSink extends UrlRedirectSink {
ServletUrlRedirectSink() {
exists(MethodAccess ma |
ma.getMethod() instanceof HttpServletResponseSendRedirectMethod and
this.asExpr() = ma.getArgument(0)
)
or
exists(MethodAccess ma |
ma.getMethod() instanceof ResponseSetHeaderMethod or
ma.getMethod() instanceof ResponseAddHeaderMethod
|
ma.getArgument(0).(CompileTimeConstantExpr).getStringValue() = "Location" and
this.asExpr() = ma.getArgument(1)
)
}
}

View File

@@ -1,6 +1,8 @@
import java
import semmle.code.java.frameworks.Servlets
import semmle.code.java.frameworks.android.WebView
import semmle.code.java.frameworks.spring.SpringController
import semmle.code.java.frameworks.spring.SpringHttp
import semmle.code.java.dataflow.TaintTracking
/*
@@ -30,6 +32,48 @@ class XssSink extends DataFlow::ExprNode {
m.getAReference().getArgument(1) = this.getExpr() and m.getName() = "loadDataWithBaseURL"
)
)
or
exists(SpringRequestMappingMethod requestMappingMethod, ReturnStmt rs |
requestMappingMethod = rs.getEnclosingCallable() and
this.asExpr() = rs.getResult() and
(
not exists(requestMappingMethod.getProduces()) or
requestMappingMethod.getProduces().matches("text/%")
)
|
// If a Spring request mapping method is either annotated with @ResponseBody (or equivalent),
// or returns a HttpEntity or sub-type, then the return value of the method is converted into
// a HTTP reponse using a HttpMessageConverter implementation. The implementation is chosen
// based on the return type of the method, and the Accept header of the request.
//
// By default, the only message converter which produces a response which is vulnerable to
// XSS is the StringHttpMessageConverter, which "Accept"s all text/* content types, including
// text/html. Therefore, if a browser request includes "text/html" in the "Accept" header,
// any String returned will be converted into a text/html response.
requestMappingMethod.isResponseBody() and
requestMappingMethod.getReturnType() instanceof TypeString
or
exists(Type returnType |
// A return type of HttpEntity<T> or ResponseEntity<T> represents an HTTP response with both
// a body and a set of headers. The body is subject to the same HttpMessageConverter
// process as above.
returnType = requestMappingMethod.getReturnType() and
(
returnType instanceof SpringHttpEntity
or
returnType instanceof SpringResponseEntity
)
|
// The type argument, representing the type of the body, is type String
returnType.(ParameterizedClass).getTypeArgument(0) instanceof TypeString
or
// Return type is a Raw class, which means no static type information on the body. In this
// case we will still treat this as an XSS sink, but rely on our taint flow steps for
// HttpEntity/ResponseEntity to only pass taint into those instances if the body type was
// String.
returnType instanceof RawClass
)
)
}
}
@@ -53,6 +97,7 @@ class WritingMethod extends Method {
(
this.getName().matches("print%") or
this.getName() = "append" or
this.getName() = "format" or
this.getName() = "write"
)
}

View File

@@ -116,7 +116,7 @@ class XMLFile extends XMLParent, File {
XMLFile() { xmlEncoding(this, _) }
/** Gets a printable representation of this XML file. */
override string toString() { result = XMLParent.super.toString() }
override string toString() { result = getName() }
/** Gets the name of this XML file. */
override string getName() { result = File.super.getAbsolutePath() }
@@ -236,7 +236,7 @@ class XMLElement extends @xmlelement, XMLParent, XMLLocatable {
string getAttributeValue(string name) { result = this.getAttribute(name).getValue() }
/** Gets a printable representation of this XML element. */
override string toString() { result = XMLParent.super.toString() }
override string toString() { result = getName() }
}
/**