Merge branch 'main' into atorralba/java/promote-xxe-experimental-sinks

This commit is contained in:
Tony Torralba
2023-05-16 09:49:34 +02:00
committed by GitHub
1736 changed files with 117920 additions and 89092 deletions

View File

@@ -1,3 +1,7 @@
## 0.6.1
No user-facing changes.
## 0.6.0
### New Queries

View File

@@ -18,10 +18,10 @@ import semmle.code.java.frameworks.spring.Spring
class InstanceFieldWrite extends FieldWrite {
InstanceFieldWrite() {
// Must be in an instance callable
not getEnclosingCallable().isStatic() and
not this.getEnclosingCallable().isStatic() and
// Must be declared in this type or a supertype.
getEnclosingCallable().getDeclaringType().inherits(getField()) and
isOwnFieldAccess()
this.getEnclosingCallable().getDeclaringType().inherits(this.getField()) and
this.isOwnFieldAccess()
}
}
@@ -62,7 +62,7 @@ class SpringPureClass extends Class {
SpringPureClass() {
// The only permitted statement in static initializers is the initialization of a static
// final or effectively final logger fields, or effectively immutable types.
forall(Stmt s | s = getANestedStmt(getAMember().(StaticInitializer).getBody()) |
forall(Stmt s | s = getANestedStmt(this.getAMember().(StaticInitializer).getBody()) |
exists(Field f | f = s.(ExprStmt).getExpr().(AssignExpr).getDest().(FieldWrite).getField() |
(
// A logger field
@@ -79,8 +79,8 @@ class SpringPureClass extends Class {
// No constructor, instance initializer or Spring bean init or setter method that is impure.
not exists(Callable c, ImpureStmt impureStmt |
(
inherits(c.(Method)) or
c = getAMember()
this.inherits(c.(Method)) or
c = this.getAMember()
) and
impureStmt.getEnclosingCallable() = c
|
@@ -110,7 +110,7 @@ class SpringPureClass extends Class {
*/
class SpringBeanFactory extends ClassOrInterface {
SpringBeanFactory() {
getAnAncestor().hasQualifiedName("org.springframework.beans.factory", "BeanFactory")
this.getAnAncestor().hasQualifiedName("org.springframework.beans.factory", "BeanFactory")
}
/**
@@ -136,20 +136,20 @@ class LiveSpringBean extends SpringBean {
LiveSpringBean() {
// Must not be needed for side effects due to construction
// Only loaded by the container when required, so construction cannot have any useful side-effects
not isLazyInit() and
not this.isLazyInit() and
// or has no side-effects when constructed
not getClass() instanceof SpringPureClass
not this.getClass() instanceof SpringPureClass
or
(
// If the class does not exist for this bean, or the class is not a source bean, then this is
// likely to be a definition using a library class, in which case we should consider it to be
// live.
not exists(getClass())
not exists(this.getClass())
or
not getClass().fromSource()
not this.getClass().fromSource()
or
// In alfresco, "webscript" beans should be considered live
getBeanParent*().getBeanParentName() = "webscript"
this.getBeanParent*().getBeanParentName() = "webscript"
or
// A live child bean implies this bean is live
exists(LiveSpringBean child | this = child.getBeanParent())

View File

@@ -3,8 +3,8 @@ import java
/** A class that implements `java.lang.Iterable`. */
class Iterable extends Class {
Iterable() {
isSourceDeclaration() and
getASourceSupertype+().hasQualifiedName("java.lang", "Iterable")
this.isSourceDeclaration() and
this.getASourceSupertype+().hasQualifiedName("java.lang", "Iterable")
}
/** The return value of a one-statement `iterator()` method. */

View File

@@ -16,7 +16,7 @@ import IterableClass
/** An `Iterable` that is also its own `Iterator`. */
class IterableIterator extends Iterable {
IterableIterator() { simpleIterator() instanceof ThisAccess }
IterableIterator() { this.simpleIterator() instanceof ThisAccess }
}
/** An `IterableIterator` that never returns any elements. */

View File

@@ -17,9 +17,9 @@ library class BoundKind extends string {
predicate isUpper() { this = "<=" }
predicate providesLowerBound() { isEqual() or isLower() }
predicate providesLowerBound() { this.isEqual() or this.isLower() }
predicate providesUpperBound() { isEqual() or isUpper() }
predicate providesUpperBound() { this.isEqual() or this.isUpper() }
}
/**

View File

@@ -19,19 +19,19 @@ import java
*/
class ArrayCast extends CastExpr {
ArrayCast() {
getExpr() instanceof ArrayCreationExpr and
getType() instanceof Array
this.getExpr() instanceof ArrayCreationExpr and
this.getType() instanceof Array
}
/** The type of the operand expression of this cast. */
Array getSourceType() { result = getExpr().getType() }
Array getSourceType() { result = this.getExpr().getType() }
/** The result type of this cast. */
Array getTargetType() { result = getType() }
Array getTargetType() { result = this.getType() }
Type getSourceComponentType() { result = getSourceType().getComponentType() }
Type getSourceComponentType() { result = this.getSourceType().getComponentType() }
Type getTargetComponentType() { result = getTargetType().getComponentType() }
Type getTargetComponentType() { result = this.getTargetType().getComponentType() }
}
predicate uncheckedCastType(RefType t) {

View File

@@ -12,6 +12,7 @@
* external/cwe/cwe-088
*/
import java
import semmle.code.java.security.CommandLineQuery
import LocalUserInputToArgumentToExecFlow::PathGraph

View File

@@ -12,17 +12,7 @@
*/
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.security.XSS
module XssLocalConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof LocalUserInput }
predicate isSink(DataFlow::Node sink) { sink instanceof XssSink }
}
module XssLocalFlow = TaintTracking::Global<XssLocalConfig>;
import semmle.code.java.security.XssLocalQuery
import XssLocalFlow::PathGraph
from XssLocalFlow::PathNode source, XssLocalFlow::PathNode sink

View File

@@ -15,28 +15,7 @@
import java
import semmle.code.java.security.SqlConcatenatedLib
import semmle.code.java.security.SqlInjectionQuery
class UncontrolledStringBuilderSource extends DataFlow::ExprNode {
UncontrolledStringBuilderSource() {
exists(StringBuilderVar sbv |
uncontrolledStringBuilderQuery(sbv, _) and
this.getExpr() = sbv.getToStringCall()
)
}
}
module UncontrolledStringBuilderSourceFlowConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node src) { src instanceof UncontrolledStringBuilderSource }
predicate isSink(DataFlow::Node sink) { sink instanceof QueryInjectionSink }
predicate isBarrier(DataFlow::Node node) {
node.getType() instanceof PrimitiveType or node.getType() instanceof BoxedType
}
}
module UncontrolledStringBuilderSourceFlow =
TaintTracking::Global<UncontrolledStringBuilderSourceFlowConfig>;
import semmle.code.java.security.SqlConcatenatedQuery
from QueryInjectionSink query, Expr uncontrolled
where

View File

@@ -12,27 +12,8 @@
* external/cwe/cwe-564
*/
import semmle.code.java.Expr
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.security.SqlInjectionQuery
module LocalUserInputToQueryInjectionFlowConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node src) { src instanceof LocalUserInput }
predicate isSink(DataFlow::Node sink) { sink instanceof QueryInjectionSink }
predicate isBarrier(DataFlow::Node node) {
node.getType() instanceof PrimitiveType or node.getType() instanceof BoxedType
}
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
any(AdditionalQueryInjectionTaintStep s).step(node1, node2)
}
}
module LocalUserInputToQueryInjectionFlow =
TaintTracking::Global<LocalUserInputToQueryInjectionFlowConfig>;
import java
import semmle.code.java.security.SqlTaintedLocalQuery
import LocalUserInputToQueryInjectionFlow::PathGraph
from

View File

@@ -12,26 +12,11 @@
*/
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.security.ResponseSplitting
import semmle.code.java.security.ResponseSplittingLocalQuery
import ResponseSplittingLocalFlow::PathGraph
module ResponseSplittingLocalConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof LocalUserInput }
predicate isSink(DataFlow::Node sink) { sink instanceof HeaderSplittingSink }
predicate isBarrier(DataFlow::Node node) {
node.getType() instanceof PrimitiveType or
node.getType() instanceof BoxedType
}
}
module ResponseSplitting = TaintTracking::Global<ResponseSplittingLocalConfig>;
import ResponseSplitting::PathGraph
from ResponseSplitting::PathNode source, ResponseSplitting::PathNode sink
where ResponseSplitting::flowPath(source, sink)
from ResponseSplittingLocalFlow::PathNode source, ResponseSplittingLocalFlow::PathNode sink
where ResponseSplittingLocalFlow::flowPath(source, sink)
select sink.getNode(), source, sink,
"This header depends on a $@, which may cause a response-splitting vulnerability.",
source.getNode(), "user-provided value"

View File

@@ -1,155 +0,0 @@
import java
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.DefUse
import semmle.code.java.security.RandomDataSource
private import BoundingChecks
/**
* If the `Array` accessed by the `ArrayAccess` is a fixed size, return the array size.
*/
int fixedArraySize(ArrayAccess arrayAccess) {
exists(Variable v |
v.getAnAccess() = arrayAccess.getArray() and
result = v.getAnAssignedValue().(ArrayCreationExpr).getFirstDimensionSize()
)
}
/**
* Holds if an `ArrayIndexOutOfBoundsException` is ever caught.
*/
private predicate arrayIndexOutOfBoundExceptionCaught(ArrayAccess arrayAccess) {
exists(TryStmt ts, CatchClause cc, RefType exc |
(
ts.getBlock().getAChild*() = arrayAccess.getEnclosingStmt() or
ts.getAResourceDecl().getAChild*() = arrayAccess.getEnclosingStmt() or
ts.getAResourceExpr().getAChildExpr*() = arrayAccess
) and
cc = ts.getACatchClause() and
exc = cc.getVariable().getType() and
exc.hasQualifiedName("java.lang", "ArrayIndexOutOfBoundsException")
)
}
/**
* A pointless loop, of the type seen frequently in Juliet tests, of the form:
*
* ```
* while(true) {
* ...
* break;
* }
* ```
*/
class PointlessLoop extends WhileStmt {
PointlessLoop() {
this.getCondition().(BooleanLiteral).getBooleanValue() = true and
// The only `break` must be the last statement.
forall(BreakStmt break | break.getTarget() = this |
this.getStmt().(BlockStmt).getLastStmt() = break
) and
// No `continue` statements.
not exists(ContinueStmt continue | continue.getTarget() = this)
}
}
/**
* An `ArrayAccess` for which we can determine whether the index is appropriately bound checked.
*
* We only consider first dimension array accesses, and we only consider indices in loops, if it's
* obvious that the loop only executes once.
*/
class CheckableArrayAccess extends ArrayAccess {
CheckableArrayAccess() {
// We are not interested in array accesses that don't access the first dimension.
not this.getArray() instanceof ArrayAccess and
// Array accesses within loops can make it difficult to verify whether the index is checked
// prior to access. Ignore "pointless" loops of the sort found in Juliet test cases.
not exists(LoopStmt loop |
loop.getBody().getAChild*() = this.getEnclosingStmt() and
not loop instanceof PointlessLoop
) and
// The possible exception is not caught
not arrayIndexOutOfBoundExceptionCaught(this)
}
/**
* Holds if we believe this indexing expression can throw an `ArrayIndexOutOfBoundsException`.
*/
predicate canThrowOutOfBounds(Expr index) {
index = this.getIndexExpr() and
not (
// There is a condition dominating this expression ensuring that the index is >= 0.
lowerBound(index) >= 0 and
// There is a condition dominating this expression that ensures the index is less than the length.
lessthanLength(this)
)
}
/**
* Holds if we believe this indexing expression can throw an `ArrayIndexOutOfBoundsException` due
* to the array being initialized with `sizeExpr`, which may be zero.
*/
predicate canThrowOutOfBoundsDueToEmptyArray(Expr sizeExpr, ArrayCreationExpr arrayCreation) {
// Find an `ArrayCreationExpr` for the array used in this indexing operation.
exists(VariableAssign assign |
assign.getSource() = arrayCreation and
defUsePair(assign, this.getArray())
) and
// If the array access is protected by a conditional that verifies the index is less than the array
// length, then the array will never be accessed if the size is zero.
not lessthanLength(this) and
// Verify that the size expression is never checked to be greater than 0.
sizeExpr = arrayCreation.getDimension(0) and
not lowerBound(sizeExpr) > 0
}
}
/**
* A source of "flow" which has an upper or lower bound.
*/
abstract class BoundedFlowSource extends DataFlow::Node {
/**
* Return a lower bound for the input, if possible.
*/
abstract int lowerBound();
/**
* Return an upper bound for the input, if possible.
*/
abstract int upperBound();
/**
* Return a description for this flow source, suitable for putting in an alert message.
*/
abstract string getDescription();
}
/**
* Input that is constructed using a random value.
*/
class RandomValueFlowSource extends BoundedFlowSource {
RandomDataSource nextAccess;
RandomValueFlowSource() { this.asExpr() = nextAccess }
override int lowerBound() { result = nextAccess.getLowerBound() }
override int upperBound() { result = nextAccess.getUpperBound() }
override string getDescription() { result = "Random value" }
}
/**
* A compile time constant expression that evaluates to a numeric type.
*/
class NumericLiteralFlowSource extends BoundedFlowSource {
NumericLiteralFlowSource() { exists(this.asExpr().(CompileTimeConstantExpr).getIntValue()) }
override int lowerBound() { result = this.asExpr().(CompileTimeConstantExpr).getIntValue() }
override int upperBound() { result = this.asExpr().(CompileTimeConstantExpr).getIntValue() }
override string getDescription() {
result = "Literal value " + this.asExpr().(CompileTimeConstantExpr).getIntValue()
}
}

View File

@@ -1,65 +0,0 @@
/**
* Provides classes and predicates for determining upper and lower bounds on a value determined by bounding checks that
* have been made on dominant paths.
*/
import java
private import semmle.code.java.controlflow.Guards
/**
* Holds if the given `ComparisonExpr` is thought to be true when `VarAccess` is accessed.
*/
private predicate conditionHolds(ComparisonExpr ce, VarAccess va) {
exists(ConditionBlock cond |
cond.getCondition() = ce and
cond.controls(va.getBasicBlock(), true)
)
}
/**
* Determine an inclusive lower-bound - if possible - for the value accessed by the given `VarAccess`,
* based upon the conditionals that hold at the point the variable is accessed.
*/
int lowerBound(VarAccess va) {
exists(ComparisonExpr greaterThanValue |
// This condition should hold when the variable is later accessed.
conditionHolds(greaterThanValue, va)
|
greaterThanValue.getGreaterOperand() = va.getVariable().getAnAccess() and
if greaterThanValue.isStrict()
then
// value > i, so value has a lower bound of i + 1
result = greaterThanValue.getLesserOperand().(CompileTimeConstantExpr).getIntValue() + 1
else
// value >= i, so value has a lower bound of i
result = greaterThanValue.getLesserOperand().(CompileTimeConstantExpr).getIntValue()
)
}
/**
* Holds if the index expression is a `VarAccess`, where the variable has been confirmed to be less
* than the length.
*/
predicate lessthanLength(ArrayAccess a) {
exists(ComparisonExpr lessThanLength, VarAccess va |
va = a.getIndexExpr() and
conditionHolds(lessThanLength, va)
|
lessThanLength.getGreaterOperand().(FieldAccess).getQualifier() = arrayReference(a) and
lessThanLength.getGreaterOperand().(FieldAccess).getField().hasName("length") and
lessThanLength.getLesserOperand() = va.getVariable().getAnAccess() and
lessThanLength.isStrict()
)
}
/**
* Return all other references to the array accessed in the `ArrayAccess`.
*/
pragma[nomagic]
private Expr arrayReference(ArrayAccess arrayAccess) {
// Array is stored in a variable.
result = arrayAccess.getArray().(VarAccess).getVariable().getAnAccess()
or
// Array is returned from a method.
result.(MethodAccess).getMethod() = arrayAccess.getArray().(MethodAccess).getMethod()
}

View File

@@ -11,20 +11,8 @@
*/
import java
import ArraySizing
import semmle.code.java.dataflow.FlowSources
private module ImproperValidationOfArrayConstructionConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
predicate isSink(DataFlow::Node sink) {
any(CheckableArrayAccess caa).canThrowOutOfBoundsDueToEmptyArray(sink.asExpr(), _)
}
}
module ImproperValidationOfArrayConstructionFlow =
TaintTracking::Global<ImproperValidationOfArrayConstructionConfig>;
import semmle.code.java.security.internal.ArraySizing
import semmle.code.java.security.ImproperValidationOfArrayConstructionQuery
import ImproperValidationOfArrayConstructionFlow::PathGraph
from

View File

@@ -12,23 +12,8 @@
*/
import java
import ArraySizing
import semmle.code.java.dataflow.TaintTracking
module BoundedFlowSourceConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
source instanceof BoundedFlowSource and
// There is not a fixed lower bound which is greater than zero.
not source.(BoundedFlowSource).lowerBound() > 0
}
predicate isSink(DataFlow::Node sink) {
any(CheckableArrayAccess caa).canThrowOutOfBoundsDueToEmptyArray(sink.asExpr(), _)
}
}
module BoundedFlowSourceFlow = DataFlow::Global<BoundedFlowSourceConfig>;
import semmle.code.java.security.internal.ArraySizing
import semmle.code.java.security.ImproperValidationOfArrayConstructionCodeSpecifiedQuery
import BoundedFlowSourceFlow::PathGraph
from

View File

@@ -12,20 +12,8 @@
*/
import java
import ArraySizing
import semmle.code.java.dataflow.FlowSources
module ImproperValidationOfArrayConstructionLocalConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof LocalUserInput }
predicate isSink(DataFlow::Node sink) {
any(CheckableArrayAccess caa).canThrowOutOfBoundsDueToEmptyArray(sink.asExpr(), _)
}
}
module ImproperValidationOfArrayConstructionLocalFlow =
TaintTracking::Global<ImproperValidationOfArrayConstructionLocalConfig>;
import semmle.code.java.security.internal.ArraySizing
import semmle.code.java.security.ImproperValidationOfArrayConstructionLocalQuery
import ImproperValidationOfArrayConstructionLocalFlow::PathGraph
from

View File

@@ -11,22 +11,8 @@
*/
import java
import ArraySizing
import semmle.code.java.dataflow.FlowSources
module ImproperValidationOfArrayIndexConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
predicate isSink(DataFlow::Node sink) {
any(CheckableArrayAccess caa).canThrowOutOfBounds(sink.asExpr())
}
predicate isBarrier(DataFlow::Node node) { node.getType() instanceof BooleanType }
}
module ImproperValidationOfArrayIndexFlow =
TaintTracking::Global<ImproperValidationOfArrayIndexConfig>;
import semmle.code.java.security.internal.ArraySizing
import semmle.code.java.security.ImproperValidationOfArrayIndexQuery
import ImproperValidationOfArrayIndexFlow::PathGraph
from

View File

@@ -12,20 +12,9 @@
*/
import java
import ArraySizing
import BoundingChecks
import semmle.code.java.dataflow.TaintTracking
module BoundedFlowSourceConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof BoundedFlowSource }
predicate isSink(DataFlow::Node sink) {
exists(CheckableArrayAccess arrayAccess | arrayAccess.canThrowOutOfBounds(sink.asExpr()))
}
}
module BoundedFlowSourceFlow = DataFlow::Global<BoundedFlowSourceConfig>;
import semmle.code.java.security.internal.ArraySizing
import semmle.code.java.security.internal.BoundingChecks
import semmle.code.java.security.ImproperValidationOfArrayIndexCodeSpecifiedQuery
import BoundedFlowSourceFlow::PathGraph
from

View File

@@ -12,20 +12,8 @@
*/
import java
import ArraySizing
import semmle.code.java.dataflow.FlowSources
module ImproperValidationOfArrayIndexLocalConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof LocalUserInput }
predicate isSink(DataFlow::Node sink) {
any(CheckableArrayAccess caa).canThrowOutOfBounds(sink.asExpr())
}
}
module ImproperValidationOfArrayIndexLocalFlow =
TaintTracking::Global<ImproperValidationOfArrayIndexLocalConfig>;
import semmle.code.java.security.internal.ArraySizing
import semmle.code.java.security.ImproperValidationOfArrayIndexLocalQuery
import ImproperValidationOfArrayIndexLocalFlow::PathGraph
from

View File

@@ -11,20 +11,8 @@
*/
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.StringFormat
module ExternallyControlledFormatStringLocalConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof LocalUserInput }
predicate isSink(DataFlow::Node sink) {
sink.asExpr() = any(StringFormat formatCall).getFormatArgument()
}
}
module ExternallyControlledFormatStringLocalFlow =
TaintTracking::Global<ExternallyControlledFormatStringLocalConfig>;
import semmle.code.java.security.ExternallyControlledFormatStringLocalQuery
import ExternallyControlledFormatStringLocalFlow::PathGraph
from

View File

@@ -1,235 +0,0 @@
import semmle.code.java.arithmetic.Overflow
import semmle.code.java.controlflow.Guards
private import semmle.code.java.dataflow.SSA
private import semmle.code.java.dataflow.DataFlow
private import semmle.code.java.dataflow.RangeAnalysis
private import semmle.code.java.dataflow.RangeUtils
private import semmle.code.java.dataflow.SignAnalysis
private import semmle.code.java.controlflow.internal.GuardsLogic
/**
* Holds if the type of `exp` is narrower than or equal to `numType`,
* or there is an enclosing cast to a type at least as narrow as 'numType'.
*/
predicate narrowerThanOrEqualTo(ArithExpr exp, NumType numType) {
exp.getType().(NumType).widerThan(numType)
implies
exists(CastingExpr cast | cast.getAChildExpr() = exp | numType.widerThanOrEqualTo(cast.getType()))
}
private Guard sizeGuard(SsaVariable v, boolean branch, boolean upper) {
exists(ComparisonExpr comp | comp = result |
comp.getLesserOperand() = ssaRead(v, 0) and
(
branch = true and upper = true
or
branch = false and upper = false
)
or
comp.getGreaterOperand() = ssaRead(v, 0) and
(
branch = true and upper = false
or
branch = false and upper = true
)
or
exists(MethodAccess ma |
ma.getMethod() instanceof MethodAbs and
ma.getArgument(0) = ssaRead(v, 0) and
(
comp.getLesserOperand() = ma and branch = true
or
comp.getGreaterOperand() = ma and branch = false
) and
(upper = false or upper = true)
)
or
// overflow test
exists(AddExpr add, RValue use, Expr pos |
use = ssaRead(v, 0) and
add.hasOperands(use, pos) and
positive(use) and
positive(pos) and
upper = true
|
comp.getLesserOperand() = add and
comp.getGreaterOperand().(IntegerLiteral).getIntValue() = 0 and
branch = false
or
comp.getGreaterOperand() = add and
comp.getLesserOperand().(IntegerLiteral).getIntValue() = 0 and
branch = true
)
)
or
result.isEquality(ssaRead(v, 0), _, branch) and
(upper = true or upper = false)
or
exists(MethodAccess call, Method m, int ix |
call = result and
call.getArgument(ix) = ssaRead(v, 0) and
call.getMethod().getSourceDeclaration() = m and
m = customSizeGuard(ix, branch, upper)
)
}
private Guard derivedSizeGuard(SsaVariable v, boolean branch, boolean upper) {
result = sizeGuard(v, branch, upper) or
exists(boolean branch0 | implies_v3(result, branch, derivedSizeGuard(v, branch0, upper), branch0))
}
private Method customSizeGuard(int index, boolean retval, boolean upper) {
exists(Parameter p, SsaImplicitInit v |
result.getReturnType().(PrimitiveType).hasName("boolean") and
not result.isOverridable() and
p.getCallable() = result and
not p.isVarargs() and
p.getType() instanceof NumericOrCharType and
p.getPosition() = index and
v.isParameterDefinition(p) and
forex(ReturnStmt ret |
ret.getEnclosingCallable() = result and
exists(Expr res | res = ret.getResult() |
not res.(BooleanLiteral).getBooleanValue() = retval.booleanNot()
)
|
ret.getResult() = derivedSizeGuard(v, retval, upper)
)
)
}
/**
* Holds if `e` is bounded in a way that is likely to prevent overflow.
*/
predicate guardedLessThanSomething(Expr e) {
exists(SsaVariable v, Guard guard, boolean branch |
e = v.getAUse() and
guard = sizeGuard(v.getAPhiInputOrPriorDef*(), branch, true) and
guard.controls(e.getBasicBlock(), branch)
)
or
negative(e)
or
e.(MethodAccess).getMethod() instanceof MethodMathMin
}
/**
* Holds if `e` is bounded in a way that is likely to prevent underflow.
*/
predicate guardedGreaterThanSomething(Expr e) {
exists(SsaVariable v, Guard guard, boolean branch |
e = v.getAUse() and
guard = sizeGuard(v.getAPhiInputOrPriorDef*(), branch, false) and
guard.controls(e.getBasicBlock(), branch)
)
or
positive(e)
or
e.(MethodAccess).getMethod() instanceof MethodMathMax
}
/** Holds if `e` occurs in a context where it will be upcast to a wider type. */
predicate upcastToWiderType(Expr e) {
exists(NumType t1, NumType t2 |
t1 = e.getType() and
(
t2.widerThan(t1)
or
t1 instanceof CharacterType and t2.getWidthRank() >= 3
)
|
exists(Variable v | v.getAnAssignedValue() = e and t2 = v.getType())
or
exists(CastingExpr c | c.getExpr() = e and t2 = c.getType())
or
exists(ReturnStmt ret | ret.getResult() = e and t2 = ret.getEnclosingCallable().getReturnType())
or
exists(Parameter p | p.getAnArgument() = e and t2 = p.getType())
or
exists(ConditionalExpr cond | cond.getABranchExpr() = e | t2 = cond.getType())
)
}
/** Holds if the result of `exp` has certain bits filtered by a bitwise and. */
private predicate inBitwiseAnd(Expr exp) {
exists(AndBitwiseExpr a | a.getAnOperand() = exp) or
inBitwiseAnd(exp.(LeftShiftExpr).getAnOperand()) or
inBitwiseAnd(exp.(RightShiftExpr).getAnOperand()) or
inBitwiseAnd(exp.(UnsignedRightShiftExpr).getAnOperand())
}
/** Holds if overflow/underflow is irrelevant for this expression. */
predicate overflowIrrelevant(Expr exp) {
inBitwiseAnd(exp) or
exp.getEnclosingCallable() instanceof HashCodeMethod
}
/**
* Holds if `n` is unlikely to be part in a path from some source containing
* numeric data to some arithmetic expression that may overflow/underflow.
*/
private predicate unlikelyNode(DataFlow::Node n) {
n.getTypeBound() instanceof TypeObject and
not exists(CastingExpr cast |
DataFlow::localFlow(n, DataFlow::exprNode(cast.getExpr())) and
cast.getType() instanceof NumericOrCharType
)
}
/** Holds if `n` is likely guarded against overflow. */
predicate overflowBarrier(DataFlow::Node n) {
n.getType() instanceof BooleanType or
guardedLessThanSomething(n.asExpr()) or
unlikelyNode(n) or
upcastToWiderType(n.asExpr()) or
overflowIrrelevant(n.asExpr())
}
/** Holds if `n` is likely guarded against underflow. */
predicate underflowBarrier(DataFlow::Node n) {
n.getType() instanceof BooleanType or
guardedGreaterThanSomething(n.asExpr()) or
unlikelyNode(n) or
upcastToWiderType(n.asExpr()) or
overflowIrrelevant(n.asExpr())
}
/**
* Holds if `use` is an operand of `exp` that acts as a sink for
* overflow-related dataflow.
*/
predicate overflowSink(ArithExpr exp, VarAccess use) {
exp.getAnOperand() = use and
(
// overflow unlikely for subtraction and division
exp instanceof AddExpr or
exp instanceof PreIncExpr or
exp instanceof PostIncExpr or
exp instanceof MulExpr
) and
not guardedLessThanSomething(use) and
// Exclude widening conversions of tainted values due to binary numeric promotion (JLS 5.6.2)
// unless there is an enclosing cast down to a narrower type.
narrowerThanOrEqualTo(exp, use.getType()) and
not overflowIrrelevant(exp)
}
/**
* Holds if `use` is an operand of `exp` that acts as a sink for
* underflow-related dataflow.
*/
predicate underflowSink(ArithExpr exp, VarAccess use) {
exp.getAnOperand() = use and
(
// underflow unlikely for addition and division
exp.(SubExpr).getLeftOperand() = use or
exp instanceof PreDecExpr or
exp instanceof PostDecExpr or
exp instanceof MulExpr
) and
not guardedGreaterThanSomething(use) and
// Exclude widening conversions of tainted values due to binary numeric promotion (JLS 5.6.2)
// unless there is an enclosing cast down to a narrower type.
narrowerThanOrEqualTo(exp, use.getType()) and
not overflowIrrelevant(exp)
}

View File

@@ -13,28 +13,9 @@
*/
import java
import semmle.code.java.dataflow.FlowSources
import ArithmeticCommon
module RemoteUserInputOverflowConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
predicate isSink(DataFlow::Node sink) { overflowSink(_, sink.asExpr()) }
predicate isBarrier(DataFlow::Node n) { overflowBarrier(n) }
}
module RemoteUserInputUnderflowConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
predicate isSink(DataFlow::Node sink) { underflowSink(_, sink.asExpr()) }
predicate isBarrier(DataFlow::Node n) { underflowBarrier(n) }
}
module RemoteUserInputOverflow = TaintTracking::Global<RemoteUserInputOverflowConfig>;
module RemoteUserInputUnderflow = TaintTracking::Global<RemoteUserInputUnderflowConfig>;
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.security.ArithmeticCommon
import semmle.code.java.security.ArithmeticTaintedQuery
module Flow =
DataFlow::MergePathGraph<RemoteUserInputOverflow::PathNode, RemoteUserInputUnderflow::PathNode,

View File

@@ -13,30 +13,9 @@
*/
import java
import semmle.code.java.dataflow.FlowSources
import ArithmeticCommon
module ArithmeticTaintedLocalOverflowConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof LocalUserInput }
predicate isSink(DataFlow::Node sink) { overflowSink(_, sink.asExpr()) }
predicate isBarrier(DataFlow::Node n) { overflowBarrier(n) }
}
module ArithmeticTaintedLocalOverflowFlow =
TaintTracking::Global<ArithmeticTaintedLocalOverflowConfig>;
module ArithmeticTaintedLocalUnderflowConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof LocalUserInput }
predicate isSink(DataFlow::Node sink) { underflowSink(_, sink.asExpr()) }
predicate isBarrier(DataFlow::Node n) { underflowBarrier(n) }
}
module ArithmeticTaintedLocalUnderflowFlow =
TaintTracking::Global<ArithmeticTaintedLocalUnderflowConfig>;
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.security.ArithmeticCommon
import semmle.code.java.security.ArithmeticTaintedLocalQuery
module Flow =
DataFlow::MergePathGraph<ArithmeticTaintedLocalOverflowFlow::PathNode,

View File

@@ -13,38 +13,9 @@
*/
import java
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.security.RandomQuery
import semmle.code.java.security.SecurityTests
import ArithmeticCommon
class TaintSource extends DataFlow::ExprNode {
TaintSource() {
exists(RandomDataSource m | not m.resultMayBeBounded() | m.getOutput() = this.getExpr())
}
}
module ArithmeticUncontrolledOverflowConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof TaintSource }
predicate isSink(DataFlow::Node sink) { overflowSink(_, sink.asExpr()) }
predicate isBarrier(DataFlow::Node n) { overflowBarrier(n) }
}
module ArithmeticUncontrolledOverflowFlow =
TaintTracking::Global<ArithmeticUncontrolledOverflowConfig>;
module ArithmeticUncontrolledUnderflowConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof TaintSource }
predicate isSink(DataFlow::Node sink) { underflowSink(_, sink.asExpr()) }
predicate isBarrier(DataFlow::Node n) { underflowBarrier(n) }
}
module ArithmeticUncontrolledUnderflowFlow =
TaintTracking::Global<ArithmeticUncontrolledUnderflowConfig>;
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.security.ArithmeticCommon
import semmle.code.java.security.ArithmeticUncontrolledQuery
module Flow =
DataFlow::MergePathGraph<ArithmeticUncontrolledOverflowFlow::PathNode,

View File

@@ -15,58 +15,14 @@
import java
import semmle.code.java.dataflow.DataFlow
import ArithmeticCommon
abstract class ExtremeValueField extends Field {
ExtremeValueField() { this.getType() instanceof IntegralType }
}
class MinValueField extends ExtremeValueField {
MinValueField() { this.getName() = "MIN_VALUE" }
}
class MaxValueField extends ExtremeValueField {
MaxValueField() { this.getName() = "MAX_VALUE" }
}
class ExtremeSource extends VarAccess {
ExtremeSource() { this.getVariable() instanceof ExtremeValueField }
}
module MaxValueFlowConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
source.asExpr().(ExtremeSource).getVariable() instanceof MaxValueField
}
predicate isSink(DataFlow::Node sink) { overflowSink(_, sink.asExpr()) }
predicate isBarrierIn(DataFlow::Node n) { isSource(n) }
predicate isBarrier(DataFlow::Node n) { overflowBarrier(n) }
}
module MaxValueFlow = DataFlow::Global<MaxValueFlowConfig>;
module MinValueFlowConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
source.asExpr().(ExtremeSource).getVariable() instanceof MinValueField
}
predicate isSink(DataFlow::Node sink) { underflowSink(_, sink.asExpr()) }
predicate isBarrierIn(DataFlow::Node n) { isSource(n) }
predicate isBarrier(DataFlow::Node n) { underflowBarrier(n) }
}
module MinValueFlow = DataFlow::Global<MinValueFlowConfig>;
import semmle.code.java.security.ArithmeticCommon
import semmle.code.java.security.ArithmeticWithExtremeValuesQuery
import Flow::PathGraph
module Flow =
DataFlow::MergePathGraph<MaxValueFlow::PathNode, MinValueFlow::PathNode, MaxValueFlow::PathGraph,
MinValueFlow::PathGraph>;
import Flow::PathGraph
predicate query(
Flow::PathNode source, Flow::PathNode sink, ArithExpr exp, string effect, Type srctyp
) {

View File

@@ -32,9 +32,9 @@ class LTWideningComparison extends WideningComparison {
leftWidth(this) < rightWidth(this)
}
override Expr getNarrower() { result = getLeftOperand() }
override Expr getNarrower() { result = this.getLeftOperand() }
override Expr getWider() { result = getRightOperand() }
override Expr getWider() { result = this.getRightOperand() }
}
class GTWideningComparison extends WideningComparison {
@@ -43,9 +43,9 @@ class GTWideningComparison extends WideningComparison {
leftWidth(this) > rightWidth(this)
}
override Expr getNarrower() { result = getRightOperand() }
override Expr getNarrower() { result = this.getRightOperand() }
override Expr getWider() { result = getLeftOperand() }
override Expr getWider() { result = this.getLeftOperand() }
}
from WideningComparison c, LoopStmt l

View File

@@ -12,228 +12,9 @@
*/
import java
import semmle.code.java.os.OSCheck
import TempDirUtils
import semmle.code.java.dataflow.TaintTracking
abstract private class MethodFileSystemFileCreation extends Method {
MethodFileSystemFileCreation() { this.getDeclaringType() instanceof TypeFile }
}
private class MethodFileDirectoryCreation extends MethodFileSystemFileCreation {
MethodFileDirectoryCreation() { this.hasName(["mkdir", "mkdirs"]) }
}
private class MethodFileFileCreation extends MethodFileSystemFileCreation {
MethodFileFileCreation() { this.hasName("createNewFile") }
}
abstract private class FileCreationSink extends DataFlow::Node { }
/**
* The qualifier of a call to one of `File`'s file-creating or directory-creating methods,
* treated as a sink by `TempDirSystemGetPropertyToCreateConfig`.
*/
private class FileFileCreationSink extends FileCreationSink {
FileFileCreationSink() {
exists(MethodAccess ma |
ma.getMethod() instanceof MethodFileSystemFileCreation and
ma.getQualifier() = this.asExpr()
)
}
}
/**
* The argument to a call to one of `Files` file-creating or directory-creating methods,
* treated as a sink by `TempDirSystemGetPropertyToCreateConfig`.
*/
private class FilesFileCreationSink extends FileCreationSink {
FilesFileCreationSink() {
exists(FilesVulnerableCreationMethodAccess ma | ma.getArgument(0) = this.asExpr())
}
}
/**
* A call to a `Files` method that create files/directories without explicitly
* setting the newly-created file or directory's permissions.
*/
private class FilesVulnerableCreationMethodAccess extends MethodAccess {
FilesVulnerableCreationMethodAccess() {
exists(Method m |
m = this.getMethod() and
m.getDeclaringType().hasQualifiedName("java.nio.file", "Files")
|
m.hasName(["write", "newBufferedWriter", "newOutputStream"])
or
m.hasName(["createFile", "createDirectory", "createDirectories"]) and
this.getNumArgument() = 1
or
m.hasName("newByteChannel") and
this.getNumArgument() = 2
)
}
}
/**
* A call to a `File` method that create files/directories with a specific set of permissions explicitly set.
* We can safely assume that any calls to these methods with explicit `PosixFilePermissions.asFileAttribute`
* contains a certain level of intentionality behind it.
*/
private class FilesSanitizingCreationMethodAccess extends MethodAccess {
FilesSanitizingCreationMethodAccess() {
exists(Method m |
m = this.getMethod() and
m.getDeclaringType().hasQualifiedName("java.nio.file", "Files")
|
m.hasName(["createFile", "createDirectory", "createDirectories"]) and
this.getNumArgument() = 2
)
}
}
/**
* The temp directory argument to a call to `java.io.File::createTempFile`,
* treated as a sink by `TempDirSystemGetPropertyToCreateConfig`.
*/
private class FileCreateTempFileSink extends FileCreationSink {
FileCreateTempFileSink() {
exists(MethodAccess ma |
ma.getMethod() instanceof MethodFileCreateTempFile and ma.getArgument(2) = this.asExpr()
)
}
}
/**
* A sanitizer that holds when the program is definitely running under some version of Windows.
*/
abstract private class WindowsOsSanitizer extends DataFlow::Node { }
private class IsNotUnixSanitizer extends WindowsOsSanitizer {
IsNotUnixSanitizer() { any(IsUnixGuard guard).controls(this.asExpr().getBasicBlock(), false) }
}
private class IsWindowsSanitizer extends WindowsOsSanitizer {
IsWindowsSanitizer() { any(IsWindowsGuard guard).controls(this.asExpr().getBasicBlock(), true) }
}
private class IsSpecificWindowsSanitizer extends WindowsOsSanitizer {
IsSpecificWindowsSanitizer() {
any(IsSpecificWindowsVariant guard).controls(this.asExpr().getBasicBlock(), true)
}
}
/**
* A taint tracking configuration tracking the access of the system temporary directory
* flowing to the creation of files or directories.
*/
module TempDirSystemGetPropertyToCreateConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
source.asExpr() instanceof ExprSystemGetPropertyTempDirTainted
}
predicate isSink(DataFlow::Node sink) {
sink instanceof FileCreationSink and
not TempDirSystemGetPropertyDirectlyToMkdir::flowTo(sink)
}
predicate isBarrier(DataFlow::Node sanitizer) {
exists(FilesSanitizingCreationMethodAccess sanitisingMethodAccess |
sanitizer.asExpr() = sanitisingMethodAccess.getArgument(0)
)
or
sanitizer instanceof WindowsOsSanitizer
}
}
module TempDirSystemGetPropertyToCreate =
TaintTracking::Global<TempDirSystemGetPropertyToCreateConfig>;
/**
* Configuration that tracks calls to to `mkdir` or `mkdirs` that are are directly on the temp directory system property.
* Examples:
* - `File tempDir = new File(System.getProperty("java.io.tmpdir")); tempDir.mkdir();`
* - `File tempDir = new File(System.getProperty("java.io.tmpdir")); tempDir.mkdirs();`
*
* These are examples of code that is simply verifying that the temp directory exists.
* As such, this code pattern is filtered out as an explicit vulnerability in
* `TempDirSystemGetPropertyToCreateConfig::isSink`.
*/
module TempDirSystemGetPropertyDirectlyToMkdirConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node node) {
exists(ExprSystemGetPropertyTempDirTainted propertyGetExpr, DataFlow::Node callSite |
DataFlow::localFlow(DataFlow::exprNode(propertyGetExpr), callSite)
|
isFileConstructorArgument(callSite.asExpr(), node.asExpr(), 1)
)
}
predicate isSink(DataFlow::Node node) {
exists(MethodAccess ma | ma.getMethod() instanceof MethodFileDirectoryCreation |
ma.getQualifier() = node.asExpr()
)
}
predicate isBarrier(DataFlow::Node sanitizer) {
isFileConstructorArgument(sanitizer.asExpr(), _, _)
}
}
module TempDirSystemGetPropertyDirectlyToMkdir =
TaintTracking::Global<TempDirSystemGetPropertyDirectlyToMkdirConfig>;
//
// Begin configuration for tracking single-method calls that are vulnerable.
//
/**
* A `MethodAccess` against a method that creates a temporary file or directory in a shared temporary directory.
*/
abstract class MethodAccessInsecureFileCreation extends MethodAccess {
/**
* Gets the type of entity created (e.g. `file`, `directory`, ...).
*/
abstract string getFileSystemEntityType();
DataFlow::Node getNode() { result.asExpr() = this }
}
/**
* An insecure call to `java.io.File.createTempFile`.
*/
class MethodAccessInsecureFileCreateTempFile extends MethodAccessInsecureFileCreation {
MethodAccessInsecureFileCreateTempFile() {
this.getMethod() instanceof MethodFileCreateTempFile and
(
// `File.createTempFile(string, string)` always uses the default temporary directory
this.getNumArgument() = 2
or
// The default temporary directory is used when the last argument of `File.createTempFile(string, string, File)` is `null`
DataFlow::localExprFlow(any(NullLiteral n), this.getArgument(2))
)
}
override string getFileSystemEntityType() { result = "file" }
}
/**
* The `com.google.common.io.Files.createTempDir` method.
*/
class MethodGuavaFilesCreateTempFile extends Method {
MethodGuavaFilesCreateTempFile() {
this.getDeclaringType().hasQualifiedName("com.google.common.io", "Files") and
this.hasName("createTempDir")
}
}
/**
* A call to the `com.google.common.io.Files.createTempDir` method.
*/
class MethodAccessInsecureGuavaFilesCreateTempFile extends MethodAccessInsecureFileCreation {
MethodAccessInsecureGuavaFilesCreateTempFile() {
this.getMethod() instanceof MethodGuavaFilesCreateTempFile
}
override string getFileSystemEntityType() { result = "directory" }
}
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.security.TempDirUtils
import semmle.code.java.security.TempDirLocalInformationDisclosureQuery
/**
* We include use of inherently insecure methods, which don't have any associated

View File

@@ -1,83 +0,0 @@
/**
* Provides classes and predicates for reasoning about temporary file/directory creations.
*/
import java
private import semmle.code.java.environment.SystemProperty
import semmle.code.java.dataflow.FlowSources
/**
* A method or field access that returns a `String` or `File` that has been tainted by `System.getProperty("java.io.tmpdir")`.
*/
class ExprSystemGetPropertyTempDirTainted extends Expr {
ExprSystemGetPropertyTempDirTainted() { this = getSystemProperty("java.io.tmpdir") }
}
/**
* A `java.io.File::createTempFile` method.
*/
class MethodFileCreateTempFile extends Method {
MethodFileCreateTempFile() {
this.getDeclaringType() instanceof TypeFile and
this.hasName("createTempFile")
}
}
/**
* Holds if `expDest` is some constructor call `new java.io.File(expSource)`, where the specific `File` constructor being used has `paramCount` parameters.
*/
predicate isFileConstructorArgument(Expr expSource, Expr exprDest, int paramCount) {
exists(ConstructorCall construtorCall |
construtorCall.getConstructedType() instanceof TypeFile and
construtorCall.getArgument(0) = expSource and
construtorCall = exprDest and
construtorCall.getConstructor().getNumberOfParameters() = paramCount
)
}
/**
* A method call to `java.io.File::setReadable`.
*/
private class FileSetRedableMethodAccess extends MethodAccess {
FileSetRedableMethodAccess() {
exists(Method m | this.getMethod() = m |
m.getDeclaringType() instanceof TypeFile and
m.hasName("setReadable")
)
}
predicate isCallWithArguments(boolean arg1, boolean arg2) {
this.isCallWithArgument(0, arg1) and this.isCallToSecondArgumentWithValue(arg2)
}
private predicate isCallToSecondArgumentWithValue(boolean value) {
this.getMethod().getNumberOfParameters() = 1 and value = true
or
this.isCallWithArgument(1, value)
}
private predicate isCallWithArgument(int index, boolean arg) {
DataFlow::localExprFlow(any(CompileTimeConstantExpr e | e.getBooleanValue() = arg),
this.getArgument(index))
}
}
/**
* Hold's if temporary directory's use is protected if there is an explicit call to
* `setReadable(false, false)`, then `setRedabale(true, true)`.
*/
predicate isPermissionsProtectedTempDirUse(DataFlow::Node sink) {
exists(FileSetRedableMethodAccess setReadable1, FileSetRedableMethodAccess setReadable2 |
setReadable1.isCallWithArguments(false, false) and
setReadable2.isCallWithArguments(true, true)
|
exists(DataFlow::Node setReadableNode1, DataFlow::Node setReadableNode2 |
setReadableNode1.asExpr() = setReadable1.getQualifier() and
setReadableNode2.asExpr() = setReadable2.getQualifier()
|
DataFlow::localFlow(sink, setReadableNode1) and // Flow from sink to setReadable(false, false)
DataFlow::localFlow(sink, setReadableNode2) and // Flow from sink to setReadable(true, true)
DataFlow::localFlow(setReadableNode1, setReadableNode2) // Flow from setReadable(false, false) to setReadable(true, true)
)
)
}

View File

@@ -15,129 +15,11 @@
import java
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.security.InformationLeak
/**
* One of the `printStackTrace()` overloads on `Throwable`.
*/
class PrintStackTraceMethod extends Method {
PrintStackTraceMethod() {
this.getDeclaringType()
.getSourceDeclaration()
.getASourceSupertype*()
.hasQualifiedName("java.lang", "Throwable") and
this.getName() = "printStackTrace"
}
}
module ServletWriterSourceToPrintStackTraceMethodFlowConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node src) { src.asExpr() instanceof XssVulnerableWriterSource }
predicate isSink(DataFlow::Node sink) {
exists(MethodAccess ma |
sink.asExpr() = ma.getAnArgument() and ma.getMethod() instanceof PrintStackTraceMethod
)
}
}
module ServletWriterSourceToPrintStackTraceMethodFlow =
TaintTracking::Global<ServletWriterSourceToPrintStackTraceMethodFlowConfig>;
/**
* A call that uses `Throwable.printStackTrace()` on a stream that is connected
* to external output.
*/
predicate printsStackToWriter(MethodAccess call) {
exists(PrintStackTraceMethod printStackTrace |
call.getMethod() = printStackTrace and
ServletWriterSourceToPrintStackTraceMethodFlow::flowToExpr(call.getAnArgument())
)
}
/**
* A `PrintWriter` that wraps a given string writer. This pattern is used
* in the most common idiom for converting a `Throwable` to a string.
*/
predicate printWriterOnStringWriter(Expr printWriter, Variable stringWriterVar) {
printWriter.getType().(Class).hasQualifiedName("java.io", "PrintWriter") and
stringWriterVar.getType().(Class).hasQualifiedName("java.io", "StringWriter") and
(
printWriter.(ClassInstanceExpr).getAnArgument() = stringWriterVar.getAnAccess() or
printWriterOnStringWriter(printWriter.(VarAccess).getVariable().getInitializer(),
stringWriterVar)
)
}
predicate stackTraceExpr(Expr exception, MethodAccess stackTraceString) {
exists(Expr printWriter, Variable stringWriterVar, MethodAccess printStackCall |
printWriterOnStringWriter(printWriter, stringWriterVar) and
printStackCall.getMethod() instanceof PrintStackTraceMethod and
printStackCall.getAnArgument() = printWriter and
printStackCall.getQualifier() = exception and
stackTraceString.getQualifier() = stringWriterVar.getAnAccess() and
stackTraceString.getMethod() instanceof ToStringMethod
)
}
module StackTraceStringToHttpResponseSinkFlowConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node src) { stackTraceExpr(_, src.asExpr()) }
predicate isSink(DataFlow::Node sink) { sink instanceof InformationLeakSink }
}
module StackTraceStringToHttpResponseSinkFlow =
TaintTracking::Global<StackTraceStringToHttpResponseSinkFlowConfig>;
/**
* A write of stack trace data to an external stream.
*/
predicate printsStackExternally(MethodAccess call, Expr stackTrace) {
printsStackToWriter(call) and
call.getQualifier() = stackTrace and
not call.getQualifier() instanceof SuperAccess
}
/**
* A stringified stack trace flows to an external sink.
*/
predicate stringifiedStackFlowsExternally(DataFlow::Node externalExpr, Expr stackTrace) {
exists(MethodAccess stackTraceString |
stackTraceExpr(stackTrace, stackTraceString) and
StackTraceStringToHttpResponseSinkFlow::flow(DataFlow::exprNode(stackTraceString), externalExpr)
)
}
class GetMessageFlowSource extends MethodAccess {
GetMessageFlowSource() {
exists(Method method |
method = this.getMethod() and
method.hasName("getMessage") and
method.hasNoParameters() and
method.getDeclaringType().hasQualifiedName("java.lang", "Throwable")
)
}
}
module GetMessageFlowSourceToHttpResponseSinkFlowConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node src) { src.asExpr() instanceof GetMessageFlowSource }
predicate isSink(DataFlow::Node sink) { sink instanceof InformationLeakSink }
}
module GetMessageFlowSourceToHttpResponseSinkFlow =
TaintTracking::Global<GetMessageFlowSourceToHttpResponseSinkFlowConfig>;
/**
* A call to `getMessage()` that then flows to a servlet response.
*/
predicate getMessageFlowsExternally(DataFlow::Node externalExpr, GetMessageFlowSource getMessage) {
GetMessageFlowSourceToHttpResponseSinkFlow::flow(DataFlow::exprNode(getMessage), externalExpr)
}
import semmle.code.java.security.StackTraceExposureQuery
from Expr externalExpr, Expr errorInformation
where
printsStackExternally(externalExpr, errorInformation) or
stringifiedStackFlowsExternally(DataFlow::exprNode(externalExpr), errorInformation) or
getMessageFlowsExternally(DataFlow::exprNode(externalExpr), errorInformation)
getMessageFlowsExternally(DataFlow::exprNode(externalExpr), DataFlow::exprNode(errorInformation))
select externalExpr, "$@ can be exposed to an external user.", errorInformation, "Error information"

View File

@@ -11,109 +11,9 @@
*/
import java
import semmle.code.java.controlflow.Guards
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.security.Encryption
import semmle.code.java.security.SecurityFlag
private import semmle.code.java.dataflow.ExternalFlow
/**
* Holds if `m` always returns `true` ignoring any exceptional flow.
*/
private predicate alwaysReturnsTrue(HostnameVerifierVerify m) {
forex(ReturnStmt rs | rs.getEnclosingCallable() = m |
rs.getResult().(CompileTimeConstantExpr).getBooleanValue() = true
)
}
/**
* A class that overrides the `javax.net.ssl.HostnameVerifier.verify` method and **always** returns `true` (though it could also exit due to an uncaught exception), thus
* accepting any certificate despite a hostname mismatch.
*/
class TrustAllHostnameVerifier extends RefType {
TrustAllHostnameVerifier() {
this.getAnAncestor() instanceof HostnameVerifier and
exists(HostnameVerifierVerify m |
m.getDeclaringType() = this and
alwaysReturnsTrue(m)
)
}
}
/**
* A configuration to model the flow of a `TrustAllHostnameVerifier` to a `set(Default)HostnameVerifier` call.
*/
module TrustAllHostnameVerifierConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
source.asExpr().(ClassInstanceExpr).getConstructedType() instanceof TrustAllHostnameVerifier
}
predicate isSink(DataFlow::Node sink) { sink instanceof HostnameVerifierSink }
predicate isBarrier(DataFlow::Node barrier) {
// ignore nodes that are in functions that intentionally disable hostname verification
barrier
.getEnclosingCallable()
.getName()
/*
* Regex: (_)* :
* some methods have underscores.
* Regex: (no|ignore|disable)(strictssl|ssl|verify|verification|hostname)
* noStrictSSL ignoreSsl
* Regex: (set)?(accept|trust|ignore|allow)(all|every|any)
* acceptAll trustAll ignoreAll setTrustAnyHttps
* Regex: (use|do|enable)insecure
* useInsecureSSL
* Regex: (set|do|use)?no.*(check|validation|verify|verification)
* setNoCertificateCheck
* Regex: disable
* disableChecks
*/
.regexpMatch("^(?i)(_)*((no|ignore|disable)(strictssl|ssl|verify|verification|hostname)" +
"|(set)?(accept|trust|ignore|allow)(all|every|any)" +
"|(use|do|enable)insecure|(set|do|use)?no.*(check|validation|verify|verification)|disable).*$")
}
}
module TrustAllHostnameVerifierFlow = DataFlow::Global<TrustAllHostnameVerifierConfig>;
import semmle.code.java.security.UnsafeHostnameVerificationQuery
import TrustAllHostnameVerifierFlow::PathGraph
/**
* A sink that sets the `HostnameVerifier` on `HttpsURLConnection`.
*/
private class HostnameVerifierSink extends DataFlow::Node {
HostnameVerifierSink() { sinkNode(this, "set-hostname-verifier") }
}
/**
* Flags suggesting a deliberately unsafe `HostnameVerifier` usage.
*/
private class UnsafeHostnameVerificationFlag extends FlagKind {
UnsafeHostnameVerificationFlag() { this = "UnsafeHostnameVerificationFlag" }
bindingset[result]
override string getAFlagName() {
result
.regexpMatch("(?i).*(secure|disable|selfCert|selfSign|validat|verif|trust|ignore|nocertificatecheck).*") and
result != "equalsIgnoreCase"
}
}
/** Gets a guard that represents a (likely) flag controlling an unsafe `HostnameVerifier` use. */
private Guard getAnUnsafeHostnameVerifierFlagGuard() {
result = any(UnsafeHostnameVerificationFlag flag).getAFlag().asExpr()
}
/** Holds if `node` is guarded by a flag that suggests an intentionally insecure use. */
private predicate isNodeGuardedByFlag(DataFlow::Node node) {
exists(Guard g | g.controls(node.asExpr().getBasicBlock(), _) |
g = getASecurityFeatureFlagGuard() or g = getAnUnsafeHostnameVerifierFlagGuard()
)
}
from
TrustAllHostnameVerifierFlow::PathNode source, TrustAllHostnameVerifierFlow::PathNode sink,
RefType verifier

View File

@@ -13,41 +13,15 @@
import java
import semmle.code.java.security.Encryption
import semmle.code.java.dataflow.TaintTracking
import DataFlow
private class ShortStringLiteral extends StringLiteral {
ShortStringLiteral() { getValue().length() < 100 }
}
class BrokenAlgoLiteral extends ShortStringLiteral {
BrokenAlgoLiteral() {
getValue().regexpMatch(getInsecureAlgorithmRegex()) and
// Exclude German and French sentences.
not getValue().regexpMatch(".*\\p{IsLowercase} des \\p{IsLetter}.*")
}
}
module InsecureCryptoConfig implements ConfigSig {
predicate isSource(Node n) { n.asExpr() instanceof BrokenAlgoLiteral }
predicate isSink(Node n) { exists(CryptoAlgoSpec c | n.asExpr() = c.getAlgoSpec()) }
predicate isBarrier(DataFlow::Node node) {
node.getType() instanceof PrimitiveType or node.getType() instanceof BoxedType
}
}
module InsecureCryptoFlow = TaintTracking::Global<InsecureCryptoConfig>;
import semmle.code.java.security.BrokenCryptoAlgorithmQuery
import InsecureCryptoFlow::PathGraph
from
InsecureCryptoFlow::PathNode source, InsecureCryptoFlow::PathNode sink, CryptoAlgoSpec c,
BrokenAlgoLiteral s
InsecureCryptoFlow::PathNode source, InsecureCryptoFlow::PathNode sink, CryptoAlgoSpec spec,
BrokenAlgoLiteral algo
where
sink.getNode().asExpr() = c.getAlgoSpec() and
source.getNode().asExpr() = s and
sink.getNode().asExpr() = spec.getAlgoSpec() and
source.getNode().asExpr() = algo and
InsecureCryptoFlow::flowPath(source, sink)
select c, source, sink, "Cryptographic algorithm $@ is weak and should not be used.", s,
s.getValue()
select spec, source, sink, "Cryptographic algorithm $@ is weak and should not be used.", algo,
algo.getValue()

View File

@@ -13,56 +13,7 @@
import java
import semmle.code.java.security.Encryption
import semmle.code.java.dataflow.TaintTracking
import DataFlow
import semmle.code.java.dispatch.VirtualDispatch
private class ShortStringLiteral extends StringLiteral {
ShortStringLiteral() { this.getValue().length() < 100 }
}
class InsecureAlgoLiteral extends ShortStringLiteral {
InsecureAlgoLiteral() {
// Algorithm identifiers should be at least two characters.
this.getValue().length() > 1 and
exists(string s | s = this.getValue() |
not s.regexpMatch(getSecureAlgorithmRegex()) and
// Exclude results covered by another query.
not s.regexpMatch(getInsecureAlgorithmRegex())
)
}
}
predicate objectToString(MethodAccess ma) {
exists(ToStringMethod m |
m = ma.getMethod() and
m.getDeclaringType() instanceof TypeObject and
exprNode(ma.getQualifier()).getTypeBound().getErasure() instanceof TypeObject
)
}
class StringContainer extends RefType {
StringContainer() {
this instanceof TypeString or
this instanceof StringBuildingType or
this.hasQualifiedName("java.util", "StringTokenizer") or
this.(Array).getComponentType() instanceof StringContainer
}
}
module InsecureCryptoConfig implements ConfigSig {
predicate isSource(Node n) { n.asExpr() instanceof InsecureAlgoLiteral }
predicate isSink(Node n) { exists(CryptoAlgoSpec c | n.asExpr() = c.getAlgoSpec()) }
predicate isBarrier(Node n) {
objectToString(n.asExpr()) or
not n.getType().getErasure() instanceof StringContainer
}
}
module InsecureCryptoFlow = TaintTracking::Global<InsecureCryptoConfig>;
import semmle.code.java.security.MaybeBrokenCryptoAlgorithmQuery
import InsecureCryptoFlow::PathGraph
from

View File

@@ -12,17 +12,7 @@
*/
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.security.UrlRedirect
module UrlRedirectConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
predicate isSink(DataFlow::Node sink) { sink instanceof UrlRedirectSink }
}
module UrlRedirectFlow = TaintTracking::Global<UrlRedirectConfig>;
import semmle.code.java.security.UrlRedirectQuery
import UrlRedirectFlow::PathGraph
from UrlRedirectFlow::PathNode source, UrlRedirectFlow::PathNode sink

View File

@@ -12,17 +12,7 @@
*/
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.security.UrlRedirect
module UrlRedirectLocalConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof LocalUserInput }
predicate isSink(DataFlow::Node sink) { sink instanceof UrlRedirectSink }
}
module UrlRedirectLocalFlow = TaintTracking::Global<UrlRedirectLocalConfig>;
import semmle.code.java.security.UrlRedirectLocalQuery
import UrlRedirectLocalFlow::PathGraph
from UrlRedirectLocalFlow::PathNode source, UrlRedirectLocalFlow::PathNode sink

View File

@@ -13,41 +13,7 @@
import java
import semmle.code.java.frameworks.Servlets
import semmle.code.java.dataflow.DataFlow
predicate isSafeSecureCookieSetting(Expr e) {
e.(CompileTimeConstantExpr).getBooleanValue() = true
or
exists(Method isSecure |
isSecure.getName() = "isSecure" and
isSecure.getDeclaringType().getASourceSupertype*() instanceof ServletRequest
|
e.(MethodAccess).getMethod() = isSecure
)
}
module SecureCookieConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
exists(MethodAccess ma, Method m | ma.getMethod() = m |
m.getDeclaringType() instanceof TypeCookie and
m.getName() = "setSecure" and
source.asExpr() = ma.getQualifier() and
forex(DataFlow::Node argSource |
DataFlow::localFlow(argSource, DataFlow::exprNode(ma.getArgument(0))) and
not DataFlow::localFlowStep(_, argSource)
|
isSafeSecureCookieSetting(argSource.asExpr())
)
)
}
predicate isSink(DataFlow::Node sink) {
sink.asExpr() =
any(MethodAccess add | add.getMethod() instanceof ResponseAddCookieMethod).getArgument(0)
}
}
module SecureCookieFlow = DataFlow::Global<SecureCookieConfig>;
import semmle.code.java.security.InsecureCookieQuery
from MethodAccess add
where

View File

@@ -12,18 +12,7 @@
*/
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.security.XPath
module XPathInjectionConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
predicate isSink(DataFlow::Node sink) { sink instanceof XPathInjectionSink }
}
module XPathInjectionFlow = TaintTracking::Global<XPathInjectionConfig>;
import semmle.code.java.security.XPathInjectionQuery
import XPathInjectionFlow::PathGraph
from XPathInjectionFlow::PathNode source, XPathInjectionFlow::PathNode sink

View File

@@ -1,69 +0,0 @@
import java
import semmle.code.java.arithmetic.Overflow
import semmle.code.java.dataflow.SSA
import semmle.code.java.controlflow.Guards
import semmle.code.java.dataflow.RangeAnalysis
class NumericNarrowingCastExpr extends CastExpr {
NumericNarrowingCastExpr() {
exists(NumericType sourceType, NumericType targetType |
sourceType = getExpr().getType() and targetType = getType()
|
not targetType.(NumType).widerThanOrEqualTo(sourceType)
)
}
}
class RightShiftOp extends Expr {
RightShiftOp() {
this instanceof RightShiftExpr or
this instanceof UnsignedRightShiftExpr or
this instanceof AssignRightShiftExpr or
this instanceof AssignUnsignedRightShiftExpr
}
private Expr getLhs() {
this.(BinaryExpr).getLeftOperand() = result or
this.(Assignment).getDest() = result
}
Variable getShiftedVariable() {
getLhs() = result.getAnAccess() or
getLhs().(AndBitwiseExpr).getAnOperand() = result.getAnAccess()
}
}
predicate boundedRead(RValue read) {
exists(SsaVariable v, ConditionBlock cb, ComparisonExpr comp, boolean testIsTrue |
read = v.getAUse() and
cb.controls(read.getBasicBlock(), testIsTrue) and
cb.getCondition() = comp
|
comp.getLesserOperand() = v.getAUse() and testIsTrue = true
or
comp.getGreaterOperand() = v.getAUse() and testIsTrue = false
)
}
predicate castCheck(RValue read) {
exists(EqualityTest eq, CastExpr cast |
cast.getExpr() = read and
eq.hasOperands(cast, read.getVariable().getAnAccess())
)
}
class SmallType extends Type {
SmallType() {
this instanceof BooleanType or
this.(PrimitiveType).hasName("byte") or
this.(BoxedType).getPrimitiveType().hasName("byte")
}
}
predicate smallExpr(Expr e) {
exists(int low, int high |
bounded(e, any(ZeroBound zb), low, false, _) and
bounded(e, any(ZeroBound zb), high, true, _) and
high - low < 256
)
}

View File

@@ -13,29 +13,7 @@
*/
import java
import semmle.code.java.dataflow.FlowSources
import NumericCastCommon
module NumericCastFlowConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }
predicate isSink(DataFlow::Node sink) {
sink.asExpr() = any(NumericNarrowingCastExpr cast).getExpr() and
sink.asExpr() instanceof VarAccess
}
predicate isBarrier(DataFlow::Node node) {
boundedRead(node.asExpr()) or
castCheck(node.asExpr()) or
node.getType() instanceof SmallType or
smallExpr(node.asExpr()) or
node.getEnclosingCallable() instanceof HashCodeMethod or
exists(RightShiftOp e | e.getShiftedVariable().getAnAccess() = node.asExpr())
}
}
module NumericCastFlow = TaintTracking::Global<NumericCastFlowConfig>;
import semmle.code.java.security.NumericCastTaintedQuery
import NumericCastFlow::PathGraph
from NumericCastFlow::PathNode source, NumericCastFlow::PathNode sink, NumericNarrowingCastExpr exp

View File

@@ -13,36 +13,16 @@
*/
import java
import semmle.code.java.dataflow.FlowSources
import NumericCastCommon
module NumericCastFlowConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node src) { src instanceof LocalUserInput }
predicate isSink(DataFlow::Node sink) {
sink.asExpr() = any(NumericNarrowingCastExpr cast).getExpr()
}
predicate isBarrier(DataFlow::Node node) {
boundedRead(node.asExpr()) or
castCheck(node.asExpr()) or
node.getType() instanceof SmallType or
smallExpr(node.asExpr()) or
node.getEnclosingCallable() instanceof HashCodeMethod
}
}
module NumericCastFlow = TaintTracking::Global<NumericCastFlowConfig>;
import NumericCastFlow::PathGraph
import semmle.code.java.security.NumericCastTaintedQuery
import NumericCastLocalFlow::PathGraph
from
NumericCastFlow::PathNode source, NumericCastFlow::PathNode sink, NumericNarrowingCastExpr exp,
VarAccess tainted
NumericCastLocalFlow::PathNode source, NumericCastLocalFlow::PathNode sink,
NumericNarrowingCastExpr exp, VarAccess tainted
where
exp.getExpr() = tainted and
sink.getNode().asExpr() = tainted and
NumericCastFlow::flowPath(source, sink) and
NumericCastLocalFlow::flowPath(source, sink) and
not exists(RightShiftOp e | e.getShiftedVariable() = tainted.getVariable())
select exp, source, sink,
"This cast to a narrower type depends on a $@, potentially causing truncation.", source.getNode(),

View File

@@ -13,55 +13,7 @@
*/
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.TaintTracking
class TypeShiroSubject extends RefType {
TypeShiroSubject() { this.getQualifiedName() = "org.apache.shiro.subject.Subject" }
}
class TypeShiroWCPermission extends RefType {
TypeShiroWCPermission() {
this.getQualifiedName() = "org.apache.shiro.authz.permission.WildcardPermission"
}
}
abstract class PermissionsConstruction extends Top {
abstract Expr getInput();
}
class PermissionsCheckMethodAccess extends MethodAccess, PermissionsConstruction {
PermissionsCheckMethodAccess() {
exists(Method m | m = this.getMethod() |
m.getDeclaringType() instanceof TypeShiroSubject and
m.getName() = "isPermitted"
or
m.getName().toLowerCase().matches("%permitted%") and
m.getNumberOfParameters() = 1
)
}
override Expr getInput() { result = this.getArgument(0) }
}
class WCPermissionConstruction extends ClassInstanceExpr, PermissionsConstruction {
WCPermissionConstruction() {
this.getConstructor().getDeclaringType() instanceof TypeShiroWCPermission
}
override Expr getInput() { result = this.getArgument(0) }
}
module TaintedPermissionsCheckFlowConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof UserInput }
predicate isSink(DataFlow::Node sink) {
sink.asExpr() = any(PermissionsConstruction p).getInput()
}
}
module TaintedPermissionsCheckFlow = TaintTracking::Global<TaintedPermissionsCheckFlowConfig>;
import semmle.code.java.security.TaintedPermissionsCheckQuery
import TaintedPermissionsCheckFlow::PathGraph
from

View File

@@ -0,0 +1,60 @@
/**
* For internal use only.
*
* Defines the set of classes that endpoint scoring models can predict. Endpoint scoring models must
* only predict classes defined within this file. This file is the source of truth for the integer
* representation of each of these classes.
*/
/** A class that can be predicted by a classifier. */
abstract class EndpointType extends string {
/**
* Holds when the string matches the name of the sink / source type.
*/
bindingset[this]
EndpointType() { any() }
/**
* Gets the name of the sink/source kind for this endpoint type as used in models-as-data.
*
* See https://github.com/github/codeql/blob/44213f0144fdd54bb679ca48d68b28dcf820f7a8/java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll#LL353C11-L357C31
*/
final string getKind() { result = this }
}
/** A class for sink types that can be predicted by a classifier. */
abstract class SinkType extends EndpointType {
bindingset[this]
SinkType() { any() }
}
/** A class for source types that can be predicted by a classifier. */
abstract class SourceType extends EndpointType {
bindingset[this]
SourceType() { any() }
}
/** The `Negative` class for non-sinks. */
class NegativeSinkType extends SinkType {
NegativeSinkType() { this = "non-sink" }
}
/** A sink relevant to the SQL injection query */
class SqlSinkType extends SinkType {
SqlSinkType() { this = "sql" }
}
/** A sink relevant to the tainted path injection query. */
class TaintedPathSinkType extends SinkType {
TaintedPathSinkType() { this = "tainted-path" }
}
/** A sink relevant to the SSRF query. */
class RequestForgerySinkType extends SinkType {
RequestForgerySinkType() { this = "ssrf" }
}
/** A sink relevant to the command injection query. */
class CommandInjectionSinkType extends SinkType {
CommandInjectionSinkType() { this = "command-injection" }
}

View File

@@ -0,0 +1,331 @@
/**
* For internal use only.
*/
private import java
private import semmle.code.Location as Location
private import semmle.code.java.dataflow.DataFlow
private import semmle.code.java.dataflow.TaintTracking
private import semmle.code.java.security.PathCreation
private import semmle.code.java.dataflow.ExternalFlow as ExternalFlow
private import semmle.code.java.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
private import semmle.code.java.security.ExternalAPIs as ExternalAPIs
private import semmle.code.java.Expr as Expr
private import semmle.code.java.security.QueryInjection
private import semmle.code.java.security.RequestForgery
private import semmle.code.java.dataflow.internal.ModelExclusions as ModelExclusions
import AutomodelSharedCharacteristics as SharedCharacteristics
import AutomodelEndpointTypes as AutomodelEndpointTypes
/**
* A meta data extractor. Any Java extraction mode needs to implement exactly
* one instance of this class.
*/
abstract class MetadataExtractor extends string {
bindingset[this]
MetadataExtractor() { any() }
abstract predicate hasMetadata(
DataFlow::ParameterNode e, string package, string type, boolean subtypes, string name,
string signature, int input
);
}
newtype JavaRelatedLocationType =
MethodDoc() or
ClassDoc()
/**
* A candidates implementation for framework mode.
*
* Some important notes:
* - This mode is using parameters as endpoints.
* - Sink- and neutral-information is being used from MaD models.
* - When available, we use method- and class-java-docs as related locations.
*/
module FrameworkCandidatesImpl implements SharedCharacteristics::CandidateSig {
// for documentation of the implementations here, see the QLDoc in the CandidateSig signature module.
class Endpoint = DataFlow::ParameterNode;
class EndpointType = AutomodelEndpointTypes::EndpointType;
class NegativeEndpointType = AutomodelEndpointTypes::NegativeSinkType;
class RelatedLocation = Location::Top;
class RelatedLocationType = JavaRelatedLocationType;
// Sanitizers are currently not modeled in MaD. TODO: check if this has large negative impact.
predicate isSanitizer(Endpoint e, EndpointType t) { none() }
RelatedLocation asLocation(Endpoint e) { result = e.asParameter() }
predicate isKnownKind(string kind, string humanReadableKind, EndpointType type) {
kind = "read-file" and
humanReadableKind = "read file" and
type instanceof AutomodelEndpointTypes::TaintedPathSinkType
or
kind = "create-file" and
humanReadableKind = "create file" and
type instanceof AutomodelEndpointTypes::TaintedPathSinkType
or
kind = "sql" and
humanReadableKind = "mad modeled sql" and
type instanceof AutomodelEndpointTypes::SqlSinkType
or
kind = "open-url" and
humanReadableKind = "open url" and
type instanceof AutomodelEndpointTypes::RequestForgerySinkType
or
kind = "jdbc-url" and
humanReadableKind = "jdbc url" and
type instanceof AutomodelEndpointTypes::RequestForgerySinkType
or
kind = "command-injection" and
humanReadableKind = "command injection" and
type instanceof AutomodelEndpointTypes::CommandInjectionSinkType
}
predicate isSink(Endpoint e, string kind) {
exists(string package, string type, string name, string signature, string ext, string input |
sinkSpec(e, package, type, name, signature, ext, input) and
ExternalFlow::sinkModel(package, type, _, name, [signature, ""], ext, input, kind, _)
)
}
predicate isNeutral(Endpoint e) {
exists(string package, string type, string name, string signature |
sinkSpec(e, package, type, name, signature, _, _) and
ExternalFlow::neutralModel(package, type, name, [signature, ""], _, _)
)
}
additional predicate sinkSpec(
Endpoint e, string package, string type, string name, string signature, string ext, string input
) {
FrameworkCandidatesImpl::getCallable(e).hasQualifiedName(package, type, name) and
signature = ExternalFlow::paramsString(getCallable(e)) and
ext = "" and
exists(int paramIdx | e.isParameterOf(_, paramIdx) |
if paramIdx = -1 then input = "Argument[this]" else input = "Argument[" + paramIdx + "]"
)
}
/**
* Returns the related location for the given endpoint.
*
* Related locations can be JavaDoc comments of the class or the method.
*/
RelatedLocation getRelatedLocation(Endpoint e, RelatedLocationType type) {
type = MethodDoc() and
result = FrameworkCandidatesImpl::getCallable(e).(Documentable).getJavadoc()
or
type = ClassDoc() and
result = FrameworkCandidatesImpl::getCallable(e).getDeclaringType().(Documentable).getJavadoc()
}
/**
* Returns the callable that contains the given endpoint.
*
* Each Java mode should implement this predicate.
*/
additional Callable getCallable(Endpoint e) { result = e.getEnclosingCallable() }
}
module CharacteristicsImpl = SharedCharacteristics::SharedCharacteristics<FrameworkCandidatesImpl>;
class EndpointCharacteristic = CharacteristicsImpl::EndpointCharacteristic;
class Endpoint = FrameworkCandidatesImpl::Endpoint;
/*
* Predicates that are used to surface prompt examples and candidates for classification with an ML model.
*/
/**
* A MetadataExtractor that extracts metadata for framework mode.
*/
class FrameworkModeMetadataExtractor extends MetadataExtractor {
FrameworkModeMetadataExtractor() { this = "FrameworkModeMetadataExtractor" }
/**
* By convention, the subtypes property of the MaD declaration should only be
* true when there _can_ exist any subtypes with a different implementation.
*
* It would technically be ok to always use the value 'true', but this would
* break convention.
*/
boolean considerSubtypes(Callable callable) {
if
callable.isStatic() or
callable.getDeclaringType().isStatic() or
callable.isFinal() or
callable.getDeclaringType().isFinal()
then result = false
else result = true
}
override predicate hasMetadata(
Endpoint e, string package, string type, boolean subtypes, string name, string signature,
int input
) {
exists(Callable callable |
e.asParameter() = callable.getParameter(input) and
package = callable.getDeclaringType().getPackage().getName() and
type = callable.getDeclaringType().getErasure().(RefType).nestedName() and
subtypes = this.considerSubtypes(callable) and
name = e.toString() and
signature = ExternalFlow::paramsString(callable)
)
}
}
/*
* EndpointCharacteristic classes that are specific to Automodel for Java.
*/
/**
* A negative characteristic that indicates that an is-style boolean method is unexploitable even if it is a sink.
*
* A sink is highly unlikely to be exploitable if its callable's name starts with `is` and the callable has a boolean return
* type (e.g. `isDirectory`). These kinds of calls normally do only checks, and appear before the proper call that does
* the dangerous/interesting thing, so we want the latter to be modeled as the sink.
*
* TODO: this might filter too much, it's possible that methods with more than one parameter contain interesting sinks
*/
private class UnexploitableIsCharacteristic extends CharacteristicsImpl::NotASinkCharacteristic {
UnexploitableIsCharacteristic() { this = "unexploitable (is-style boolean method)" }
override predicate appliesToEndpoint(Endpoint e) {
not FrameworkCandidatesImpl::isSink(e, _) and
FrameworkCandidatesImpl::getCallable(e).getName().matches("is%") and
FrameworkCandidatesImpl::getCallable(e).getReturnType() instanceof BooleanType
}
}
/**
* A negative characteristic that indicates that an existence-checking boolean method is unexploitable even if it is a
* sink.
*
* A sink is highly unlikely to be exploitable if its callable's name is `exists` or `notExists` and the callable has a
* boolean return type. These kinds of calls normally do only checks, and appear before the proper call that does the
* dangerous/interesting thing, so we want the latter to be modeled as the sink.
*/
private class UnexploitableExistsCharacteristic extends CharacteristicsImpl::NotASinkCharacteristic {
UnexploitableExistsCharacteristic() { this = "unexploitable (existence-checking boolean method)" }
override predicate appliesToEndpoint(Endpoint e) {
not FrameworkCandidatesImpl::isSink(e, _) and
exists(Callable callable |
callable = FrameworkCandidatesImpl::getCallable(e) and
callable.getName().toLowerCase() = ["exists", "notexists"] and
callable.getReturnType() instanceof BooleanType
)
}
}
/**
* A negative characteristic that indicates that an endpoint is an argument to an exception, which is not a sink.
*/
private class ExceptionCharacteristic extends CharacteristicsImpl::NotASinkCharacteristic {
ExceptionCharacteristic() { this = "exception" }
override predicate appliesToEndpoint(Endpoint e) {
FrameworkCandidatesImpl::getCallable(e).getDeclaringType().getASupertype*() instanceof
TypeThrowable
}
}
/**
* A characteristic that limits candidates to parameters of methods that are recognized as `ModelApi`, iow., APIs that
* are considered worth modeling.
*/
private class NotAModelApiParameter extends CharacteristicsImpl::UninterestingToModelCharacteristic {
NotAModelApiParameter() { this = "not a model API parameter" }
override predicate appliesToEndpoint(Endpoint e) {
not exists(ModelExclusions::ModelApi api | api.getAParameter() = e.asParameter())
}
}
/**
* A negative characteristic that filters out non-public methods. Non-public methods are not interesting to include in
* the standard Java modeling, because they cannot be called from outside the package.
*/
private class NonPublicMethodCharacteristic extends CharacteristicsImpl::UninterestingToModelCharacteristic
{
NonPublicMethodCharacteristic() { this = "non-public method" }
override predicate appliesToEndpoint(Endpoint e) {
not FrameworkCandidatesImpl::getCallable(e).isPublic()
}
}
/**
* Holds if the given endpoint has a self-contradictory combination of characteristics. Detects errors in our endpoint
* characteristics. Lists the problematic characteristics and their implications for all such endpoints, together with
* an error message indicating why this combination is problematic.
*
* Copied from
* javascript/ql/experimental/adaptivethreatmodeling/test/endpoint_large_scale/ContradictoryEndpointCharacteristics.ql
*/
predicate erroneousEndpoints(
Endpoint endpoint, EndpointCharacteristic characteristic,
AutomodelEndpointTypes::EndpointType endpointType, float confidence, string errorMessage,
boolean ignoreKnownModelingErrors
) {
// An endpoint's characteristics should not include positive indicators with medium/high confidence for more than one
// sink/source type (including the negative type).
exists(
EndpointCharacteristic characteristic2, AutomodelEndpointTypes::EndpointType endpointClass2,
float confidence2
|
endpointType != endpointClass2 and
(
endpointType instanceof AutomodelEndpointTypes::SinkType and
endpointClass2 instanceof AutomodelEndpointTypes::SinkType
or
endpointType instanceof AutomodelEndpointTypes::SourceType and
endpointClass2 instanceof AutomodelEndpointTypes::SourceType
) and
characteristic.appliesToEndpoint(endpoint) and
characteristic2.appliesToEndpoint(endpoint) and
characteristic.hasImplications(endpointType, true, confidence) and
characteristic2.hasImplications(endpointClass2, true, confidence2) and
confidence > SharedCharacteristics::mediumConfidence() and
confidence2 > SharedCharacteristics::mediumConfidence() and
(
ignoreKnownModelingErrors = true and
not knownOverlappingCharacteristics(characteristic, characteristic2)
or
ignoreKnownModelingErrors = false
)
) and
errorMessage = "Endpoint has high-confidence positive indicators for multiple classes"
or
// An endpoint's characteristics should not include positive indicators with medium/high confidence for some class and
// also include negative indicators with medium/high confidence for this same class.
exists(EndpointCharacteristic characteristic2, float confidence2 |
characteristic.appliesToEndpoint(endpoint) and
characteristic2.appliesToEndpoint(endpoint) and
characteristic.hasImplications(endpointType, true, confidence) and
characteristic2.hasImplications(endpointType, false, confidence2) and
confidence > SharedCharacteristics::mediumConfidence() and
confidence2 > SharedCharacteristics::mediumConfidence()
) and
ignoreKnownModelingErrors = false and
errorMessage = "Endpoint has high-confidence positive and negative indicators for the same class"
}
/**
* Holds if `characteristic1` and `characteristic2` are among the pairs of currently known positive characteristics that
* have some overlap in their results. This indicates a problem with the underlying Java modeling. Specifically,
* `PathCreation` is prone to FPs.
*/
private predicate knownOverlappingCharacteristics(
EndpointCharacteristic characteristic1, EndpointCharacteristic characteristic2
) {
characteristic1 != characteristic2 and
characteristic1 = ["mad taint step", "create path", "read file", "known non-sink"] and
characteristic2 = ["mad taint step", "create path", "read file", "known non-sink"]
}

View File

@@ -0,0 +1,50 @@
/**
* Surfaces the endpoints that are not already known to be sinks, and are therefore used as candidates for
* classification with an ML model.
*
* Note: This query does not actually classify the endpoints using the model.
*
* @name Automodel candidates
* @description A query to extract automodel candidates.
* @kind problem
* @severity info
* @id java/ml/extract-automodel-candidates
* @tags internal automodel extract candidates
*/
private import AutomodelFrameworkModeCharacteristics
private import AutomodelSharedUtil
from
Endpoint endpoint, string message, MetadataExtractor meta, string package, string type,
boolean subtypes, string name, string signature, int input
where
not exists(CharacteristicsImpl::UninterestingToModelCharacteristic u |
u.appliesToEndpoint(endpoint)
) and
// If a node is already a known sink for any of our existing ATM queries and is already modeled as a MaD sink, we
// don't include it as a candidate. Otherwise, we might include it as a candidate for query A, but the model will
// label it as a sink for one of the sink types of query B, for which it's already a known sink. This would result in
// overlap between our detected sinks and the pre-existing modeling. We assume that, if a sink has already been
// modeled in a MaD model, then it doesn't belong to any additional sink types, and we don't need to reexamine it.
not CharacteristicsImpl::isSink(endpoint, _) and
meta.hasMetadata(endpoint, package, type, subtypes, name, signature, input) and
// The message is the concatenation of all sink types for which this endpoint is known neither to be a sink nor to be
// a non-sink, and we surface only endpoints that have at least one such sink type.
message =
strictconcat(AutomodelEndpointTypes::SinkType sinkType |
not CharacteristicsImpl::isKnownSink(endpoint, sinkType) and
CharacteristicsImpl::isSinkCandidate(endpoint, sinkType)
|
sinkType, ", "
)
select endpoint,
message + "\nrelated locations: $@, $@." + "\nmetadata: $@, $@, $@, $@, $@, $@.", //
CharacteristicsImpl::getRelatedLocationOrCandidate(endpoint, MethodDoc()), "MethodDoc", //
CharacteristicsImpl::getRelatedLocationOrCandidate(endpoint, ClassDoc()), "ClassDoc", //
package.(DollarAtString), "package", //
type.(DollarAtString), "type", //
subtypes.toString().(DollarAtString), "subtypes", //
name.(DollarAtString), "name", //
signature.(DollarAtString), "signature", //
input.toString().(DollarAtString), "input" //

View File

@@ -0,0 +1,47 @@
/**
* Surfaces endpoints that are non-sinks with high confidence, for use as negative examples in the prompt.
*
* @name Negative examples (experimental)
* @kind problem
* @severity info
* @id java/ml/non-sink
* @tags internal automodel extract examples negative
*/
private import AutomodelFrameworkModeCharacteristics
private import AutomodelEndpointTypes
private import AutomodelSharedUtil
from
Endpoint endpoint, EndpointCharacteristic characteristic, float confidence, string message,
MetadataExtractor meta, string package, string type, boolean subtypes, string name,
string signature, int input
where
characteristic.appliesToEndpoint(endpoint) and
confidence >= SharedCharacteristics::highConfidence() and
characteristic.hasImplications(any(NegativeSinkType negative), true, confidence) and
// Exclude endpoints that have contradictory endpoint characteristics, because we only want examples we're highly
// certain about in the prompt.
not erroneousEndpoints(endpoint, _, _, _, _, false) and
meta.hasMetadata(endpoint, package, type, subtypes, name, signature, input) and
// It's valid for a node to satisfy the logic for both `isSink` and `isSanitizer`, but in that case it will be
// treated by the actual query as a sanitizer, since the final logic is something like
// `isSink(n) and not isSanitizer(n)`. We don't want to include such nodes as negative examples in the prompt, because
// they're ambiguous and might confuse the model, so we explicitly exclude all known sinks from the negative examples.
not exists(EndpointCharacteristic characteristic2, float confidence2, SinkType positiveType |
not positiveType instanceof NegativeSinkType and
characteristic2.appliesToEndpoint(endpoint) and
confidence2 >= SharedCharacteristics::maximalConfidence() and
characteristic2.hasImplications(positiveType, true, confidence2)
) and
message = characteristic
select endpoint,
message + "\nrelated locations: $@, $@." + "\nmetadata: $@, $@, $@, $@, $@, $@.", //
CharacteristicsImpl::getRelatedLocationOrCandidate(endpoint, MethodDoc()), "MethodDoc", //
CharacteristicsImpl::getRelatedLocationOrCandidate(endpoint, ClassDoc()), "ClassDoc", //
package.(DollarAtString), "package", //
type.(DollarAtString), "type", //
subtypes.toString().(DollarAtString), "subtypes", //
name.(DollarAtString), "name", //
signature.(DollarAtString), "signature", //
input.toString().(DollarAtString), "input" //

View File

@@ -0,0 +1,34 @@
/**
* Surfaces endpoints that are sinks with high confidence, for use as positive examples in the prompt.
*
* @name Positive examples (experimental)
* @kind problem
* @severity info
* @id java/ml/known-sink
* @tags internal automodel extract examples positive
*/
private import AutomodelFrameworkModeCharacteristics
private import AutomodelEndpointTypes
private import AutomodelSharedUtil
from
Endpoint endpoint, SinkType sinkType, MetadataExtractor meta, string package, string type,
boolean subtypes, string name, string signature, int input
where
// Exclude endpoints that have contradictory endpoint characteristics, because we only want examples we're highly
// certain about in the prompt.
not erroneousEndpoints(endpoint, _, _, _, _, false) and
meta.hasMetadata(endpoint, package, type, subtypes, name, signature, input) and
// Extract positive examples of sinks belonging to the existing ATM query configurations.
CharacteristicsImpl::isKnownSink(endpoint, sinkType)
select endpoint,
sinkType + "\nrelated locations: $@, $@." + "\nmetadata: $@, $@, $@, $@, $@, $@.", //
CharacteristicsImpl::getRelatedLocationOrCandidate(endpoint, MethodDoc()), "MethodDoc", //
CharacteristicsImpl::getRelatedLocationOrCandidate(endpoint, ClassDoc()), "ClassDoc", //
package.(DollarAtString), "package", //
type.(DollarAtString), "type", //
subtypes.toString().(DollarAtString), "subtypes", //
name.(DollarAtString), "name", //
signature.(DollarAtString), "signature", //
input.toString().(DollarAtString), "input" //

View File

@@ -0,0 +1,305 @@
float maximalConfidence() { result = 1.0 }
float highConfidence() { result = 0.9 }
float mediumConfidence() { result = 0.6 }
/**
* A specification of how to instantiate the shared characteristics for a given candidate class.
*
* The `CandidateSig` implementation specifies a type to use for Endpoints (eg., `ParameterNode`), as well as a type
* to label endpoint classes (the `EndpointType`). One of the endpoint classes needs to be a 'negative' class, meaning
* "not any of the other known endpoint types".
*/
signature module CandidateSig {
/**
* An endpoint is a potential candidate for modeling. This will typically be bound to the language's
* DataFlow node class, or a subtype thereof.
*/
class Endpoint;
/**
* A related location for an endpoint. This will typically be bound to the supertype of all AST nodes (eg., `Top`).
*/
class RelatedLocation;
/**
* A label for a related location.
*
* Eg., method-doc, class-doc, etc.
*/
class RelatedLocationType;
/**
* A class kind for an endpoint.
*/
class EndpointType extends string;
/**
* An EndpointType that denotes the absence of any sink.
*/
class NegativeEndpointType extends EndpointType;
/**
* Gets the endpoint as a location.
*
* This is a utility function to convert an endpoint to its corresponding location.
*/
RelatedLocation asLocation(Endpoint e);
/**
* Defines what MaD kinds are known, and what endpoint type they correspond to.
*/
predicate isKnownKind(string kind, string humanReadableLabel, EndpointType type);
/**
* Holds if `e` is a flow sanitizer, and has type `t`.
*/
predicate isSanitizer(Endpoint e, EndpointType t);
/**
* Holds if `e` is a sink with the label `kind`.
*/
predicate isSink(Endpoint e, string kind);
/**
* Holds if `e` is not a sink of any kind.
*/
predicate isNeutral(Endpoint e);
/**
* Gets a related location.
*
* A related location is a source code location that may hold extra information about an endpoint that can be useful
* to the machine learning model.
*
* For example, a related location for a method call may be the documentation comment of a method.
*/
RelatedLocation getRelatedLocation(Endpoint e, RelatedLocationType name);
}
/**
* A set of shared characteristics for a given candidate class.
*
* This module is language-agnostic, although the `CandidateSig` module will be language-specific.
*
* The language specific implementation can also further extend the behavior of this module by adding additional
* implementations of endpoint characteristics exported by this module.
*/
module SharedCharacteristics<CandidateSig Candidate> {
predicate isSink = Candidate::isSink/2;
predicate isNeutral = Candidate::isNeutral/1;
/**
* Holds if `sink` is a known sink of type `endpointType`.
*/
predicate isKnownSink(Candidate::Endpoint sink, Candidate::EndpointType endpointType) {
// If the list of characteristics includes positive indicators with maximal confidence for this class, then it's a
// known sink for the class.
not endpointType instanceof Candidate::NegativeEndpointType and
exists(EndpointCharacteristic characteristic |
characteristic.appliesToEndpoint(sink) and
characteristic.hasImplications(endpointType, true, maximalConfidence())
)
}
/**
* Holds if the candidate sink `candidateSink` should be considered as a possible sink of type `sinkType`, and
* classified by the ML model. A candidate sink is a node that cannot be excluded from `sinkType` based on its
* characteristics.
*/
predicate isSinkCandidate(Candidate::Endpoint candidateSink, Candidate::EndpointType sinkType) {
not sinkType instanceof Candidate::NegativeEndpointType and
not exists(getAReasonSinkExcluded(candidateSink, sinkType))
}
/**
* Gets the related location of `e` with name `name`, if it exists.
* Otherwise, gets the candidate itself.
*/
Candidate::RelatedLocation getRelatedLocationOrCandidate(
Candidate::Endpoint e, Candidate::RelatedLocationType type
) {
if exists(Candidate::getRelatedLocation(e, type))
then result = Candidate::getRelatedLocation(e, type)
else result = Candidate::asLocation(e)
}
/**
* Gets the list of characteristics that cause `candidateSink` to be excluded as an effective sink for a given sink
* type.
*/
EndpointCharacteristic getAReasonSinkExcluded(
Candidate::Endpoint candidateSink, Candidate::EndpointType sinkType
) {
// An endpoint is a sink candidate if none of its characteristics give much indication whether or not it is a sink.
not sinkType instanceof Candidate::NegativeEndpointType and
result.appliesToEndpoint(candidateSink) and
(
// Exclude endpoints that have a characteristic that implies they're not sinks for _any_ sink type.
exists(float confidence |
confidence >= mediumConfidence() and
result.hasImplications(any(Candidate::NegativeEndpointType t), true, confidence)
)
or
// Exclude endpoints that have a characteristic that implies they're not sinks for _this particular_ sink type.
exists(float confidence |
confidence >= mediumConfidence() and
result.hasImplications(sinkType, false, confidence)
)
)
}
/**
* A set of characteristics that a particular endpoint might have. This set of characteristics is used to make decisions
* about whether to include the endpoint in the training set and with what kind, as well as whether to score the
* endpoint at inference time.
*/
abstract class EndpointCharacteristic extends string {
/**
* Holds for the string that is the name of the characteristic. This should describe some property of an endpoint
* that is meaningful for determining whether it's a sink, and if so, of which sink type.
*/
bindingset[this]
EndpointCharacteristic() { any() }
/**
* Holds for endpoints that have this characteristic.
*/
abstract predicate appliesToEndpoint(Candidate::Endpoint n);
/**
* This predicate describes what the characteristic tells us about an endpoint.
*
* Params:
* endpointType: The sink/source type.
* isPositiveIndicator: If true, this characteristic indicates that this endpoint _is_ a member of the class; if
* false, it indicates that it _isn't_ a member of the class.
* confidence: A float in [0, 1], which tells us how strong an indicator this characteristic is for the endpoint
* belonging / not belonging to the given class. A confidence near zero means this characteristic is a very weak
* indicator of whether or not the endpoint belongs to the class. A confidence of 1 means that all endpoints with
* this characteristic definitively do/don't belong to the class.
*/
abstract predicate hasImplications(
Candidate::EndpointType endpointType, boolean isPositiveIndicator, float confidence
);
/** Indicators with confidence at or above this threshold are considered to be high-confidence indicators. */
final float getHighConfidenceThreshold() { result = 0.8 }
}
/**
* A high-confidence characteristic that indicates that an endpoint is a sink of a specified type. These endpoints can
* be used as positive samples for training or for a few-shot prompt.
*/
abstract class SinkCharacteristic extends EndpointCharacteristic {
bindingset[this]
SinkCharacteristic() { any() }
abstract Candidate::EndpointType getSinkType();
final override predicate hasImplications(
Candidate::EndpointType endpointType, boolean isPositiveIndicator, float confidence
) {
endpointType = this.getSinkType() and
isPositiveIndicator = true and
confidence = maximalConfidence()
}
}
/**
* A high-confidence characteristic that indicates that an endpoint is not a sink of any type. These endpoints can be
* used as negative samples for training or for a few-shot prompt.
*/
abstract class NotASinkCharacteristic extends EndpointCharacteristic {
bindingset[this]
NotASinkCharacteristic() { any() }
override predicate hasImplications(
Candidate::EndpointType endpointType, boolean isPositiveIndicator, float confidence
) {
endpointType instanceof Candidate::NegativeEndpointType and
isPositiveIndicator = true and
confidence = highConfidence()
}
}
/**
* A medium-confidence characteristic that indicates that an endpoint is unlikely to be a sink of any type. These
* endpoints can be excluded from scoring at inference time, both to save time and to avoid false positives. They should
* not, however, be used as negative samples for training or for a few-shot prompt, because they may include a small
* number of sinks.
*/
abstract class LikelyNotASinkCharacteristic extends EndpointCharacteristic {
bindingset[this]
LikelyNotASinkCharacteristic() { any() }
override predicate hasImplications(
Candidate::EndpointType endpointType, boolean isPositiveIndicator, float confidence
) {
endpointType instanceof Candidate::NegativeEndpointType and
isPositiveIndicator = true and
confidence = mediumConfidence()
}
}
/**
* A characteristic that indicates not necessarily that an endpoint is not a sink, but rather that it is not a sink
* that's interesting to model in the standard Java libraries. These filters should be removed when extracting sink
* candidates within a user's codebase for customized modeling.
*
* These endpoints should not be used as negative samples for training or for a few-shot prompt, because they are not
* necessarily non-sinks.
*/
abstract class UninterestingToModelCharacteristic extends EndpointCharacteristic {
bindingset[this]
UninterestingToModelCharacteristic() { any() }
override predicate hasImplications(
Candidate::EndpointType endpointType, boolean isPositiveIndicator, float confidence
) {
endpointType instanceof Candidate::NegativeEndpointType and
isPositiveIndicator = true and
confidence = mediumConfidence()
}
}
/**
* Contains default implementations that are derived solely from the `CandidateSig` implementation.
*/
private module DefaultCharacteristicImplementations {
/**
* Endpoints identified as sinks by the `CandidateSig` implementation are sinks with maximal confidence.
*/
private class KnownSinkCharacteristic extends SinkCharacteristic {
string madKind;
Candidate::EndpointType endpointType;
KnownSinkCharacteristic() { Candidate::isKnownKind(madKind, this, endpointType) }
override predicate appliesToEndpoint(Candidate::Endpoint e) { Candidate::isSink(e, madKind) }
override Candidate::EndpointType getSinkType() { result = endpointType }
}
/**
* A negative characteristic that indicates that an endpoint was manually modeled as a neutral model.
*/
private class NeutralModelCharacteristic extends NotASinkCharacteristic {
NeutralModelCharacteristic() { this = "known non-sink" }
override predicate appliesToEndpoint(Candidate::Endpoint e) { Candidate::isNeutral(e) }
}
/**
* A negative characteristic that indicates that an endpoint is not part of the source code for the project being
* analyzed.
*/
private class IsSanitizerCharacteristic extends NotASinkCharacteristic {
IsSanitizerCharacteristic() { this = "external" }
override predicate appliesToEndpoint(Candidate::Endpoint e) { Candidate::isSanitizer(e, _) }
}
}
}

View File

@@ -0,0 +1,21 @@
/**
* A helper class to represent a string value that can be returned by a query using $@ notation.
*
* It extends `string`, but adds a mock `hasLocationInfo` method that returns the string itself as the file name.
*
* Use this, when you want to return a string value from a query using $@ notation - the string value
* will be included in the sarif file.
*
*
* Background information on `hasLocationInfo`:
* https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/#providing-location-information
*/
class DollarAtString extends string {
bindingset[this]
DollarAtString() { any() }
bindingset[this]
predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
path = this and sl = 1 and sc = 1 and el = 1 and ec = 1
}
}

View File

@@ -10,10 +10,6 @@ private import semmle.code.java.dataflow.internal.FlowSummaryImpl as FlowSummary
private import semmle.code.java.dataflow.TaintTracking
private import semmle.code.java.dataflow.internal.ModelExclusions
private string containerAsJar(Container container) {
if container instanceof JarFile then result = container.getBaseName() else result = "rt.jar"
}
/** Holds if the given callable is not worth supporting. */
private predicate isUninteresting(Callable c) {
c.getDeclaringType() instanceof TestLibrary or
@@ -35,10 +31,18 @@ class ExternalApi extends Callable {
"#" + this.getName() + paramsString(this)
}
private string getJarName() {
result = this.getCompilationUnit().getParentContainer*().(JarFile).getBaseName()
}
/**
* Gets the jar file containing this API. Normalizes the Java Runtime to "rt.jar" despite the presence of modules.
*/
string jarContainer() { result = containerAsJar(this.getCompilationUnit().getParentContainer*()) }
string jarContainer() {
result = this.getJarName()
or
not exists(this.getJarName()) and result = "rt.jar"
}
/** Gets a node that is an input to a call to this API. */
private DataFlow::Node getAnInput() {

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* The queries `java/xxe` and `java/xxe-local` now recognize the second argument of calls to `XPath.evaluate` as a sink.

View File

@@ -0,0 +1,3 @@
## 0.6.1
No user-facing changes.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.6.0
lastReleaseVersion: 0.6.1

View File

@@ -17,6 +17,10 @@ import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.ExternalFlow
import RemoteUrlToOpenStreamFlow::PathGraph
private class ActivateModels extends ActiveExperimentalModels {
ActivateModels() { this = "openstream-called-on-tainted-url" }
}
class UrlConstructor extends ClassInstanceExpr {
UrlConstructor() { this.getConstructor().getDeclaringType() instanceof TypeUrl }

View File

@@ -17,11 +17,11 @@ class WebRequestSource extends DataFlow::Node {
m.hasName("getParameterNames") or
m.hasName("getParameterMap")
) and
ma = asExpr()
ma = this.asExpr()
)
}
}
class WebRequest extends RefType {
WebRequest() { hasQualifiedName("org.springframework.web.context.request", "WebRequest") }
WebRequest() { this.hasQualifiedName("org.springframework.web.context.request", "WebRequest") }
}

View File

@@ -24,7 +24,7 @@ class SetRevocationEnabledSink extends DataFlow::ExprNode {
SetRevocationEnabledSink() {
exists(MethodAccess setRevocationEnabledCall |
setRevocationEnabledCall.getMethod() instanceof SetRevocationEnabledMethod and
setRevocationEnabledCall.getArgument(0) = getExpr() and
setRevocationEnabledCall.getArgument(0) = this.getExpr() and
not exists(MethodAccess ma, Method m | m = ma.getMethod() |
(m instanceof AddCertPathCheckerMethod or m instanceof SetCertPathCheckersMethod) and
ma.getQualifier().(VarAccess).getVariable() =
@@ -36,25 +36,25 @@ class SetRevocationEnabledSink extends DataFlow::ExprNode {
class SetRevocationEnabledMethod extends Method {
SetRevocationEnabledMethod() {
getDeclaringType() instanceof PKIXParameters and
hasName("setRevocationEnabled")
this.getDeclaringType() instanceof PKIXParameters and
this.hasName("setRevocationEnabled")
}
}
class AddCertPathCheckerMethod extends Method {
AddCertPathCheckerMethod() {
getDeclaringType() instanceof PKIXParameters and
hasName("addCertPathChecker")
this.getDeclaringType() instanceof PKIXParameters and
this.hasName("addCertPathChecker")
}
}
class SetCertPathCheckersMethod extends Method {
SetCertPathCheckersMethod() {
getDeclaringType() instanceof PKIXParameters and
hasName("setCertPathCheckers")
this.getDeclaringType() instanceof PKIXParameters and
this.hasName("setCertPathCheckers")
}
}
class PKIXParameters extends RefType {
PKIXParameters() { hasQualifiedName("java.security.cert", "PKIXParameters") }
PKIXParameters() { this.hasQualifiedName("java.security.cert", "PKIXParameters") }
}

View File

@@ -27,7 +27,7 @@ class SslContextGetInstanceSink extends DataFlow::ExprNode {
exists(StaticMethodAccess ma, Method m | m = ma.getMethod() |
m.getDeclaringType() instanceof SslContext and
m.hasName("getInstance") and
ma.getArgument(0) = asExpr()
ma.getArgument(0) = this.asExpr()
)
}
}
@@ -39,7 +39,7 @@ class SslContextGetInstanceSink extends DataFlow::ExprNode {
class CreateSslParametersSink extends DataFlow::ExprNode {
CreateSslParametersSink() {
exists(ConstructorCall cc | cc.getConstructedType() instanceof SslParameters |
cc.getArgument(1) = asExpr()
cc.getArgument(1) = this.asExpr()
)
}
}
@@ -53,7 +53,7 @@ class SslParametersSetProtocolsSink extends DataFlow::ExprNode {
exists(MethodAccess ma, Method m | m = ma.getMethod() |
m.getDeclaringType() instanceof SslParameters and
m.hasName("setProtocols") and
ma.getArgument(0) = asExpr()
ma.getArgument(0) = this.asExpr()
)
}
}
@@ -73,7 +73,7 @@ class SetEnabledProtocolsSink extends DataFlow::ExprNode {
type instanceof SslEngine
) and
m.hasName("setEnabledProtocols") and
ma.getArgument(0) = asExpr()
ma.getArgument(0) = this.asExpr()
)
}
}
@@ -83,15 +83,15 @@ class SetEnabledProtocolsSink extends DataFlow::ExprNode {
*/
class UnsafeTlsVersion extends StringLiteral {
UnsafeTlsVersion() {
getValue() = "SSL" or
getValue() = "SSLv2" or
getValue() = "SSLv3" or
getValue() = "TLS" or
getValue() = "TLSv1" or
getValue() = "TLSv1.1"
this.getValue() = "SSL" or
this.getValue() = "SSLv2" or
this.getValue() = "SSLv3" or
this.getValue() = "TLS" or
this.getValue() = "TLSv1" or
this.getValue() = "TLSv1.1"
}
}
class SslServerSocket extends RefType {
SslServerSocket() { hasQualifiedName("javax.net.ssl", "SSLServerSocket") }
SslServerSocket() { this.hasQualifiedName("javax.net.ssl", "SSLServerSocket") }
}

View File

@@ -31,14 +31,14 @@ private predicate setsAllowCredentials(MethodAccess header) {
private class CorsProbableCheckAccess extends MethodAccess {
CorsProbableCheckAccess() {
getMethod().hasName("contains") and
getMethod().getDeclaringType().getASourceSupertype*() instanceof CollectionType
this.getMethod().hasName("contains") and
this.getMethod().getDeclaringType().getASourceSupertype*() instanceof CollectionType
or
getMethod().hasName("containsKey") and
getMethod().getDeclaringType().getASourceSupertype*() instanceof MapType
this.getMethod().hasName("containsKey") and
this.getMethod().getDeclaringType().getASourceSupertype*() instanceof MapType
or
getMethod().hasName("equals") and
getQualifier().getType() instanceof TypeString
this.getMethod().hasName("equals") and
this.getQualifier().getType() instanceof TypeString
}
}

View File

@@ -24,10 +24,10 @@ import BindingUnsafeRemoteObjectFlow::PathGraph
private class BindMethod extends Method {
BindMethod() {
(
getDeclaringType().hasQualifiedName("java.rmi", "Naming") or
getDeclaringType().hasQualifiedName("java.rmi.registry", "Registry")
this.getDeclaringType().hasQualifiedName("java.rmi", "Naming") or
this.getDeclaringType().hasQualifiedName("java.rmi.registry", "Registry")
) and
hasName(["bind", "rebind"])
this.hasName(["bind", "rebind"])
}
}

View File

@@ -1,5 +1,5 @@
name: codeql/java-queries
version: 0.6.1-dev
version: 0.6.2-dev
groups:
- java
- queries
@@ -12,3 +12,4 @@ dependencies:
codeql/util: ${workspace}
dataExtensions:
- Telemetry/ExtractorInformation.yml
warnOnImplicitThis: true

View File

@@ -7,8 +7,9 @@
import java
import semmle.code.java.dataflow.ExternalFlow
from string package, string type, string name, string signature, string provenance
from string package, string type, string name, string signature, string kind, string provenance
where
neutralModel(package, type, name, signature, provenance) and
neutralModel(package, type, name, signature, kind, provenance) and
not provenance.matches("%generated")
select package, type, name, signature, provenance order by package, type, name, signature
select package, type, name, signature, kind, provenance order by
package, type, name, signature, kind

View File

@@ -139,9 +139,9 @@ module ThroughFlowConfig implements DataFlow::StateConfigSig {
predicate isAdditionalFlowStep(
DataFlow::Node node1, FlowState state1, DataFlow::Node node2, FlowState state2
) {
exists(DataFlowImplCommon::TypedContent tc |
DataFlowImplCommon::store(node1, tc, node2, _) and
isRelevantContent(tc.getContent()) and
exists(DataFlow::Content c |
DataFlowImplCommon::store(node1, c, node2, _, _) and
isRelevantContent(c) and
(
state1 instanceof TaintRead and state2.(TaintStore).getStep() = 1
or
@@ -178,7 +178,7 @@ string captureThroughFlow(DataFlowTargetApi api) {
string output
|
ThroughFlow::flow(p, returnNodeExt) and
returnNodeExt.getEnclosingCallable() = api and
returnNodeExt.(DataFlow::Node).getEnclosingCallable() = api and
input = parameterNodeAsInput(p) and
output = returnNodeAsOutput(returnNodeExt) and
input != output and

View File

@@ -25,8 +25,11 @@ module PrintingImpl<PrintingSig Printing> {
+ Printing::getProvenance()
}
string asNeutralModel(Printing::Api api) {
result = asPartialNeutralModel(api) + Printing::getProvenance()
string asNeutralSummaryModel(Printing::Api api) {
result =
asPartialNeutralModel(api) //
+ "summary" + ";" //
+ Printing::getProvenance()
}
/**

View File

@@ -73,7 +73,9 @@ private string isExtensible(J::RefType ref) {
}
private string typeAsModel(J::RefType type) {
result = type.getCompilationUnit().getPackage().getName() + ";" + type.nestedName()
result =
type.getCompilationUnit().getPackage().getName() + ";" +
type.getErasure().(J::RefType).nestedName()
}
private J::RefType bestTypeForModel(TargetApiSpecific api) {
@@ -184,7 +186,7 @@ string returnNodeAsOutput(DataFlowImplCommon::ReturnNodeExt node) {
exists(int pos |
pos = node.getKind().(DataFlowImplCommon::ParamUpdateReturnKind).getPosition()
|
result = parameterAccess(node.getEnclosingCallable().getParameter(pos))
result = parameterAccess(node.(DataFlow::Node).getEnclosingCallable().getParameter(pos))
or
result = qualifierString() and pos = -1
)
@@ -236,7 +238,7 @@ predicate apiSource(DataFlow::Node source) {
string asInputArgumentSpecific(DataFlow::Node source) {
exists(int pos |
source.(DataFlow::ParameterNode).isParameterOf(_, pos) and
result = "Argument[" + pos + "]"
if pos >= 0 then result = "Argument[" + pos + "]" else result = qualifierString()
)
or
source.asExpr() instanceof J::FieldAccess and

View File

@@ -78,5 +78,5 @@ string captureFlow(DataFlowTargetApi api) {
*/
string captureNoFlow(DataFlowTargetApi api) {
not exists(captureFlow(api)) and
result = ModelPrinting::asNeutralModel(api)
result = ModelPrinting::asNeutralSummaryModel(api)
}