mirror of
https://github.com/github/codeql.git
synced 2026-04-27 09:45:15 +02:00
Merge branch 'main' into kaeluka/add-provenance-to-metadata
This commit is contained in:
@@ -22,7 +22,6 @@
|
||||
"csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll",
|
||||
"go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl1.qll",
|
||||
"go/ql/lib/semmle/go/dataflow/internal/DataFlowImpl2.qll",
|
||||
"go/ql/lib/semmle/go/dataflow/internal/DataFlowImplForStringsNewReplacer.qll",
|
||||
"python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl1.qll",
|
||||
"python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl2.qll",
|
||||
"python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl3.qll",
|
||||
@@ -572,4 +571,4 @@
|
||||
"python/ql/lib/semmle/python/security/internal/EncryptionKeySizes.qll",
|
||||
"java/ql/lib/semmle/code/java/security/internal/EncryptionKeySizes.qll"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
## 0.9.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* The `shouldPrintFunction` predicate from `PrintAstConfiguration` has been replaced by `shouldPrintDeclaration`. Users should now override `shouldPrintDeclaration` if they want to limit the declarations that should be printed.
|
||||
* The `shouldPrintFunction` predicate from `PrintIRConfiguration` has been replaced by `shouldPrintDeclaration`. Users should now override `shouldPrintDeclaration` if they want to limit the declarations that should be printed.
|
||||
|
||||
### Major Analysis Improvements
|
||||
|
||||
* The `PrintAST` library now also prints global and namespace variables and their initializers.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The `_Float128x` type is no longer exposed as a builtin type. As this type could not occur any code base, this should only affect queries that explicitly looked at the builtin types.
|
||||
|
||||
## 0.8.1
|
||||
|
||||
### Deprecated APIs
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: majorAnalysis
|
||||
---
|
||||
* The `PrintAST` library now also prints global and namespace variables and their initializers.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The `_Float128x` type is no longer exposed as a builtin type. As this type could not occur any code base, this should only affect queries that explicitly looked at the builtin types.
|
||||
@@ -1,5 +1,14 @@
|
||||
---
|
||||
category: breaking
|
||||
---
|
||||
## 0.9.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* The `shouldPrintFunction` predicate from `PrintAstConfiguration` has been replaced by `shouldPrintDeclaration`. Users should now override `shouldPrintDeclaration` if they want to limit the declarations that should be printed.
|
||||
* The `shouldPrintFunction` predicate from `PrintIRConfiguration` has been replaced by `shouldPrintDeclaration`. Users should now override `shouldPrintDeclaration` if they want to limit the declarations that should be printed.
|
||||
|
||||
### Major Analysis Improvements
|
||||
|
||||
* The `PrintAST` library now also prints global and namespace variables and their initializers.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The `_Float128x` type is no longer exposed as a builtin type. As this type could not occur any code base, this should only affect queries that explicitly looked at the builtin types.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.8.1
|
||||
lastReleaseVersion: 0.9.0
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/cpp-all
|
||||
version: 0.8.2-dev
|
||||
version: 0.9.1-dev
|
||||
groups: cpp
|
||||
dbscheme: semmlecode.cpp.dbscheme
|
||||
extractor: cpp
|
||||
|
||||
@@ -1520,6 +1520,25 @@ private module Cached {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `operand.getDef() = instr`, but there exists a `StoreInstruction` that
|
||||
* writes to an address that is equivalent to the value computed by `instr` in
|
||||
* between `instr` and `operand`, and therefore there should not be flow from `*instr`
|
||||
* to `*operand`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate isStoredToBetween(Instruction instr, Operand operand) {
|
||||
simpleOperandLocalFlowStep(pragma[only_bind_into](instr), pragma[only_bind_into](operand)) and
|
||||
exists(StoreInstruction store, IRBlock block, int storeIndex, int instrIndex, int operandIndex |
|
||||
store.getDestinationAddress() = instr and
|
||||
block.getInstruction(storeIndex) = store and
|
||||
block.getInstruction(instrIndex) = instr and
|
||||
block.getInstruction(operandIndex) = operand.getUse() and
|
||||
instrIndex < storeIndex and
|
||||
storeIndex < operandIndex
|
||||
)
|
||||
}
|
||||
|
||||
private predicate indirectionInstructionFlow(
|
||||
RawIndirectInstruction nodeFrom, IndirectOperand nodeTo
|
||||
) {
|
||||
@@ -1529,7 +1548,8 @@ private module Cached {
|
||||
simpleOperandLocalFlowStep(pragma[only_bind_into](instr), pragma[only_bind_into](operand))
|
||||
|
|
||||
hasOperandAndIndex(nodeTo, operand, pragma[only_bind_into](indirectionIndex)) and
|
||||
hasInstructionAndIndex(nodeFrom, instr, pragma[only_bind_into](indirectionIndex))
|
||||
hasInstructionAndIndex(nodeFrom, instr, pragma[only_bind_into](indirectionIndex)) and
|
||||
not isStoredToBetween(instr, operand)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -94,8 +94,9 @@ predicate hasRawIndirectInstruction(Instruction instr, int indirectionIndex) {
|
||||
|
||||
cached
|
||||
private newtype TDefOrUseImpl =
|
||||
TDefImpl(Operand address, int indirectionIndex) {
|
||||
exists(Instruction base | isDef(_, _, address, base, _, indirectionIndex) |
|
||||
TDefImpl(BaseSourceVariableInstruction base, Operand address, int indirectionIndex) {
|
||||
isDef(_, _, address, base, _, indirectionIndex) and
|
||||
(
|
||||
// We only include the definition if the SSA pruning stage
|
||||
// concluded that the definition is live after the write.
|
||||
any(SsaInternals0::Def def).getAddressOperand() = address
|
||||
@@ -105,8 +106,8 @@ private newtype TDefOrUseImpl =
|
||||
base.(VariableAddressInstruction).getAstVariable() instanceof GlobalLikeVariable
|
||||
)
|
||||
} or
|
||||
TUseImpl(Operand operand, int indirectionIndex) {
|
||||
isUse(_, operand, _, _, indirectionIndex) and
|
||||
TUseImpl(BaseSourceVariableInstruction base, Operand operand, int indirectionIndex) {
|
||||
isUse(_, operand, base, _, indirectionIndex) and
|
||||
not isDef(_, _, operand, _, _, _)
|
||||
} or
|
||||
TGlobalUse(GlobalLikeVariable v, IRFunction f, int indirectionIndex) {
|
||||
@@ -193,7 +194,7 @@ abstract private class DefOrUseImpl extends TDefOrUseImpl {
|
||||
|
||||
/**
|
||||
* Gets the instruction that computes the base of this definition or use.
|
||||
* This is always a `VariableAddressInstruction` or an `AllocationInstruction`.
|
||||
* This is always a `VariableAddressInstruction` or an `CallInstruction`.
|
||||
*/
|
||||
abstract BaseSourceVariableInstruction getBase();
|
||||
|
||||
@@ -265,15 +266,17 @@ abstract class DefImpl extends DefOrUseImpl {
|
||||
}
|
||||
|
||||
private class DirectDef extends DefImpl, TDefImpl {
|
||||
DirectDef() { this = TDefImpl(address, ind) }
|
||||
BaseSourceVariableInstruction base;
|
||||
|
||||
override BaseSourceVariableInstruction getBase() { isDef(_, _, address, result, _, _) }
|
||||
DirectDef() { this = TDefImpl(base, address, ind) }
|
||||
|
||||
override int getIndirection() { isDef(_, _, address, _, result, ind) }
|
||||
override BaseSourceVariableInstruction getBase() { result = base }
|
||||
|
||||
override Node0Impl getValue() { isDef(_, result, address, _, _, _) }
|
||||
override int getIndirection() { isDef(_, _, address, base, result, ind) }
|
||||
|
||||
override predicate isCertain() { isDef(true, _, address, _, _, ind) }
|
||||
override Node0Impl getValue() { isDef(_, result, address, base, _, _) }
|
||||
|
||||
override predicate isCertain() { isDef(true, _, address, base, _, ind) }
|
||||
}
|
||||
|
||||
private class IteratorDef extends DefImpl, TIteratorDef {
|
||||
@@ -316,6 +319,7 @@ abstract class UseImpl extends DefOrUseImpl {
|
||||
|
||||
abstract private class OperandBasedUse extends UseImpl {
|
||||
Operand operand;
|
||||
BaseSourceVariableInstruction base;
|
||||
|
||||
bindingset[ind]
|
||||
OperandBasedUse() { any() }
|
||||
@@ -323,50 +327,44 @@ abstract private class OperandBasedUse extends UseImpl {
|
||||
final override predicate hasIndexInBlock(IRBlock block, int index) {
|
||||
// See the comment in `ssa0`'s `OperandBasedUse` for an explanation of this
|
||||
// predicate's implementation.
|
||||
exists(BaseSourceVariableInstruction base | base = this.getBase() |
|
||||
if base.getAst() = any(Cpp::PostfixCrementOperation c).getOperand()
|
||||
then
|
||||
exists(Operand op, int indirectionIndex, int indirection |
|
||||
indirectionIndex = this.getIndirectionIndex() and
|
||||
indirection = this.getIndirection() and
|
||||
op =
|
||||
min(Operand cand, int i |
|
||||
isUse(_, cand, base, indirection, indirectionIndex) and
|
||||
block.getInstruction(i) = cand.getUse()
|
||||
|
|
||||
cand order by i
|
||||
) and
|
||||
block.getInstruction(index) = op.getUse()
|
||||
)
|
||||
else operand.getUse() = block.getInstruction(index)
|
||||
)
|
||||
if base.getAst() = any(Cpp::PostfixCrementOperation c).getOperand()
|
||||
then
|
||||
exists(Operand op, int indirectionIndex, int indirection |
|
||||
indirectionIndex = this.getIndirectionIndex() and
|
||||
indirection = this.getIndirection() and
|
||||
op =
|
||||
min(Operand cand, int i |
|
||||
isUse(_, cand, base, indirection, indirectionIndex) and
|
||||
block.getInstruction(i) = cand.getUse()
|
||||
|
|
||||
cand order by i
|
||||
) and
|
||||
block.getInstruction(index) = op.getUse()
|
||||
)
|
||||
else operand.getUse() = block.getInstruction(index)
|
||||
}
|
||||
|
||||
final override BaseSourceVariableInstruction getBase() { result = base }
|
||||
|
||||
final Operand getOperand() { result = operand }
|
||||
|
||||
final override Cpp::Location getLocation() { result = operand.getLocation() }
|
||||
}
|
||||
|
||||
private class DirectUse extends OperandBasedUse, TUseImpl {
|
||||
DirectUse() { this = TUseImpl(operand, ind) }
|
||||
DirectUse() { this = TUseImpl(base, operand, ind) }
|
||||
|
||||
override int getIndirection() { isUse(_, operand, _, result, ind) }
|
||||
override int getIndirection() { isUse(_, operand, base, result, ind) }
|
||||
|
||||
override BaseSourceVariableInstruction getBase() { isUse(_, operand, result, _, ind) }
|
||||
|
||||
override predicate isCertain() { isUse(true, operand, _, _, ind) }
|
||||
override predicate isCertain() { isUse(true, operand, base, _, ind) }
|
||||
|
||||
override Node getNode() { nodeHasOperand(result, operand, ind) }
|
||||
}
|
||||
|
||||
private class IteratorUse extends OperandBasedUse, TIteratorUse {
|
||||
BaseSourceVariableInstruction container;
|
||||
IteratorUse() { this = TIteratorUse(operand, base, ind) }
|
||||
|
||||
IteratorUse() { this = TIteratorUse(operand, container, ind) }
|
||||
|
||||
override int getIndirection() { isIteratorUse(container, operand, result, ind) }
|
||||
|
||||
override BaseSourceVariableInstruction getBase() { result = container }
|
||||
override int getIndirection() { isIteratorUse(base, operand, result, ind) }
|
||||
|
||||
override predicate isCertain() { none() }
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ private import DataFlowImplCommon as DataFlowImplCommon
|
||||
private import DataFlowUtil
|
||||
private import semmle.code.cpp.models.interfaces.PointerWrapper
|
||||
private import DataFlowPrivate
|
||||
private import semmle.code.cpp.ir.ValueNumbering
|
||||
|
||||
/**
|
||||
* Holds if `operand` is an operand that is not used by the dataflow library.
|
||||
@@ -146,14 +147,6 @@ int countIndirectionsForCppType(LanguageType langType) {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A `CallInstruction` that calls an allocation function such
|
||||
* as `malloc` or `operator new`.
|
||||
*/
|
||||
class AllocationInstruction extends CallInstruction {
|
||||
AllocationInstruction() { this.getStaticCallTarget() instanceof Cpp::AllocationFunction }
|
||||
}
|
||||
|
||||
private predicate isIndirectionType(Type t) { t instanceof Indirection }
|
||||
|
||||
private predicate hasUnspecifiedBaseType(Indirection t, Type base) {
|
||||
@@ -368,7 +361,7 @@ newtype TBaseSourceVariable =
|
||||
// Each IR variable gets its own source variable
|
||||
TBaseIRVariable(IRVariable var) or
|
||||
// Each allocation gets its own source variable
|
||||
TBaseCallVariable(AllocationInstruction call)
|
||||
TBaseCallVariable(CallInstruction call) { not call.getResultIRType() instanceof IRVoidType }
|
||||
|
||||
abstract private class AbstractBaseSourceVariable extends TBaseSourceVariable {
|
||||
/** Gets a textual representation of this element. */
|
||||
@@ -396,11 +389,11 @@ class BaseIRVariable extends AbstractBaseSourceVariable, TBaseIRVariable {
|
||||
}
|
||||
|
||||
class BaseCallVariable extends AbstractBaseSourceVariable, TBaseCallVariable {
|
||||
AllocationInstruction call;
|
||||
CallInstruction call;
|
||||
|
||||
BaseCallVariable() { this = TBaseCallVariable(call) }
|
||||
|
||||
AllocationInstruction getCallInstruction() { result = call }
|
||||
CallInstruction getCallInstruction() { result = call }
|
||||
|
||||
override string toString() { result = call.toString() }
|
||||
|
||||
@@ -504,8 +497,7 @@ private class BaseIRVariableInstruction extends BaseSourceVariableInstruction,
|
||||
override BaseIRVariable getBaseSourceVariable() { result.getIRVariable() = this.getIRVariable() }
|
||||
}
|
||||
|
||||
private class BaseAllocationInstruction extends BaseSourceVariableInstruction, AllocationInstruction
|
||||
{
|
||||
private class BaseCallInstruction extends BaseSourceVariableInstruction, CallInstruction {
|
||||
override BaseCallVariable getBaseSourceVariable() { result.getCallInstruction() = this }
|
||||
}
|
||||
|
||||
@@ -873,7 +865,7 @@ private module Cached {
|
||||
* to a specific address.
|
||||
*/
|
||||
private predicate isCertainAddress(Operand operand) {
|
||||
operand.getDef() instanceof VariableAddressInstruction
|
||||
valueNumberOfOperand(operand).getAnInstruction() instanceof VariableAddressInstruction
|
||||
or
|
||||
operand.getType() instanceof Cpp::ReferenceType
|
||||
}
|
||||
|
||||
@@ -307,8 +307,3 @@ class SemConditionalExpr extends SemKnownExpr {
|
||||
branch = false and result = falseResult
|
||||
}
|
||||
}
|
||||
|
||||
/** Holds if `upper = true` and `e <= bound` or `upper = false` and `e >= bound`. */
|
||||
predicate semHasConstantBoundConstantSpecific(SemExpr e, float bound, boolean upper) {
|
||||
Specific::hasConstantBoundConstantSpecific(e, bound, upper)
|
||||
}
|
||||
|
||||
@@ -434,50 +434,6 @@ module SemanticExprConfig {
|
||||
|
||||
/** Gets the expression associated with `instr`. */
|
||||
SemExpr getSemanticExpr(IR::Instruction instr) { result = Equiv::getEquivalenceClass(instr) }
|
||||
|
||||
private predicate typeBounds(SemType t, float lb, float ub) {
|
||||
exists(SemIntegerType integralType, float limit |
|
||||
integralType = t and limit = 2.pow(8 * integralType.getByteSize())
|
||||
|
|
||||
if integralType instanceof SemBooleanType
|
||||
then lb = 0 and ub = 1
|
||||
else
|
||||
if integralType.isSigned()
|
||||
then (
|
||||
lb = -(limit / 2) and ub = (limit / 2) - 1
|
||||
) else (
|
||||
lb = 0 and ub = limit - 1
|
||||
)
|
||||
)
|
||||
or
|
||||
// This covers all floating point types. The range is (-Inf, +Inf).
|
||||
t instanceof SemFloatingPointType and lb = -(1.0 / 0.0) and ub = 1.0 / 0.0
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `upper = true` and `e <= bound` or `upper = false` and `e >= bound` based
|
||||
* only on type information.
|
||||
*/
|
||||
predicate hasConstantBoundConstantSpecific(Expr e, float bound, boolean upper) {
|
||||
exists(
|
||||
SemType converted, SemType unconverted, float unconvertedLb, float convertedLb,
|
||||
float unconvertedUb, float convertedUb
|
||||
|
|
||||
unconverted = getSemanticType(e.getUnconverted().getResultIRType()) and
|
||||
converted = getSemanticType(e.getConverted().getResultIRType()) and
|
||||
typeBounds(unconverted, unconvertedLb, unconvertedUb) and
|
||||
typeBounds(converted, convertedLb, convertedUb) and
|
||||
(
|
||||
upper = true and
|
||||
unconvertedUb < convertedUb and
|
||||
bound = unconvertedUb
|
||||
or
|
||||
upper = false and
|
||||
unconvertedLb > convertedLb and
|
||||
bound = unconvertedLb
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
predicate getSemanticExpr = SemanticExprConfig::getSemanticExpr/1;
|
||||
@@ -501,5 +457,3 @@ IRBound::Bound getCppBound(SemBound bound) { bound = result }
|
||||
SemGuard getSemanticGuard(IRGuards::IRGuardCondition guard) { result = guard }
|
||||
|
||||
IRGuards::IRGuardCondition getCppGuard(SemGuard guard) { guard = result }
|
||||
|
||||
predicate hasConstantBoundConstantSpecific = SemanticExprConfig::hasConstantBoundConstantSpecific/3;
|
||||
|
||||
@@ -3,24 +3,10 @@
|
||||
*/
|
||||
|
||||
private import semmle.code.cpp.rangeanalysis.new.internal.semantic.Semantic
|
||||
private import codeql.util.Unit
|
||||
private import Reason as Reason
|
||||
private import RangeAnalysisStage
|
||||
private import semmle.code.cpp.rangeanalysis.new.internal.semantic.analysis.FloatDelta
|
||||
|
||||
module CppLangImplConstant implements LangSig<FloatDelta> {
|
||||
private module Param implements Reason::ParamSig {
|
||||
class TypeReasonImpl = Unit;
|
||||
}
|
||||
|
||||
class SemReason = Reason::Make<Param>::SemReason;
|
||||
|
||||
class SemNoReason = Reason::Make<Param>::SemNoReason;
|
||||
|
||||
class SemCondReason = Reason::Make<Param>::SemCondReason;
|
||||
|
||||
class SemTypeReason = Reason::Make<Param>::SemTypeReason;
|
||||
|
||||
/**
|
||||
* Holds if the specified expression should be excluded from the result of `ssaRead()`.
|
||||
*
|
||||
@@ -74,10 +60,7 @@ module CppLangImplConstant implements LangSig<FloatDelta> {
|
||||
/**
|
||||
* Holds if `e >= bound` (if `upper = false`) or `e <= bound` (if `upper = true`).
|
||||
*/
|
||||
predicate hasConstantBound(SemExpr e, float bound, boolean upper, SemReason reason) {
|
||||
semHasConstantBoundConstantSpecific(e, bound, upper) and
|
||||
reason instanceof SemTypeReason
|
||||
}
|
||||
predicate hasConstantBound(SemExpr e, float bound, boolean upper) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `e >= bound + delta` (if `upper = false`) or `e <= bound + delta` (if `upper = true`).
|
||||
|
||||
@@ -61,23 +61,18 @@ private newtype TSemReason =
|
||||
guard = any(ConstantStage::SemCondReason reason).getCond()
|
||||
or
|
||||
guard = any(RelativeStage::SemCondReason reason).getCond()
|
||||
} or
|
||||
TSemTypeReason()
|
||||
}
|
||||
|
||||
private ConstantStage::SemReason constantReason(SemReason reason) {
|
||||
ConstantStage::SemReason constantReason(SemReason reason) {
|
||||
result instanceof ConstantStage::SemNoReason and reason instanceof SemNoReason
|
||||
or
|
||||
result.(ConstantStage::SemCondReason).getCond() = reason.(SemCondReason).getCond()
|
||||
or
|
||||
result instanceof ConstantStage::SemTypeReason and reason instanceof SemTypeReason
|
||||
}
|
||||
|
||||
private RelativeStage::SemReason relativeReason(SemReason reason) {
|
||||
RelativeStage::SemReason relativeReason(SemReason reason) {
|
||||
result instanceof RelativeStage::SemNoReason and reason instanceof SemNoReason
|
||||
or
|
||||
result.(RelativeStage::SemCondReason).getCond() = reason.(SemCondReason).getCond()
|
||||
or
|
||||
result instanceof RelativeStage::SemTypeReason and reason instanceof SemTypeReason
|
||||
}
|
||||
|
||||
import Public
|
||||
@@ -116,12 +111,4 @@ module Public {
|
||||
|
||||
override string toString() { result = this.getCond().toString() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A reason for an inferred bound that indicates that the bound is inferred
|
||||
* based on type-information.
|
||||
*/
|
||||
class SemTypeReason extends SemReason, TSemTypeReason {
|
||||
override string toString() { result = "TypeReason" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,25 +7,9 @@ private import RangeAnalysisStage
|
||||
private import semmle.code.cpp.rangeanalysis.new.internal.semantic.analysis.FloatDelta
|
||||
private import semmle.code.cpp.rangeanalysis.new.internal.semantic.analysis.IntDelta
|
||||
private import RangeAnalysisImpl
|
||||
private import codeql.util.Unit
|
||||
private import Reason as Reason
|
||||
private import semmle.code.cpp.rangeanalysis.RangeAnalysisUtils
|
||||
|
||||
module CppLangImplRelative implements LangSig<FloatDelta> {
|
||||
private module Param implements Reason::ParamSig {
|
||||
class TypeReasonImpl extends Unit {
|
||||
TypeReasonImpl() { none() }
|
||||
}
|
||||
}
|
||||
|
||||
class SemReason = Reason::Make<Param>::SemReason;
|
||||
|
||||
class SemNoReason = Reason::Make<Param>::SemNoReason;
|
||||
|
||||
class SemCondReason = Reason::Make<Param>::SemCondReason;
|
||||
|
||||
class SemTypeReason = Reason::Make<Param>::SemTypeReason;
|
||||
|
||||
/**
|
||||
* Holds if the specified expression should be excluded from the result of `ssaRead()`.
|
||||
*
|
||||
@@ -110,7 +94,7 @@ module CppLangImplRelative implements LangSig<FloatDelta> {
|
||||
/**
|
||||
* Holds if `e >= bound` (if `upper = false`) or `e <= bound` (if `upper = true`).
|
||||
*/
|
||||
predicate hasConstantBound(SemExpr e, float bound, boolean upper, SemReason reason) { none() }
|
||||
predicate hasConstantBound(SemExpr e, float bound, boolean upper) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `e >= bound + delta` (if `upper = false`) or `e <= bound + delta` (if `upper = true`).
|
||||
|
||||
@@ -113,37 +113,6 @@ signature module DeltaSig {
|
||||
}
|
||||
|
||||
signature module LangSig<DeltaSig D> {
|
||||
/** A reason for an inferred bound. */
|
||||
class SemReason {
|
||||
/**
|
||||
* Returns `this` if `reason` is not a `SemTypeReason`. Otherwise,
|
||||
* this predicate returns `SemTypeReason`.
|
||||
*
|
||||
* This predicate ensures that we propagate `SemTypeReason` all the way
|
||||
* to the top-level of a call to `semBounded` if the inferred bound is
|
||||
* based on type-information.
|
||||
*/
|
||||
bindingset[this, reason]
|
||||
SemReason combineWith(SemReason reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* A reason for an inferred bound that indicates that the bound is inferred
|
||||
* without going through a bounding condition.
|
||||
*/
|
||||
class SemNoReason extends SemReason;
|
||||
|
||||
/** A reason for an inferred bound pointing to a condition. */
|
||||
class SemCondReason extends SemReason {
|
||||
SemGuard getCond();
|
||||
}
|
||||
|
||||
/**
|
||||
* A reason for an inferred bound that indicates that the bound is inferred
|
||||
* based on type-information.
|
||||
*/
|
||||
class SemTypeReason extends SemReason;
|
||||
|
||||
/**
|
||||
* Holds if the specified expression should be excluded from the result of `ssaRead()`.
|
||||
*
|
||||
@@ -155,7 +124,7 @@ signature module LangSig<DeltaSig D> {
|
||||
/**
|
||||
* Holds if `e >= bound` (if `upper = false`) or `e <= bound` (if `upper = true`).
|
||||
*/
|
||||
predicate hasConstantBound(SemExpr e, D::Delta bound, boolean upper, SemReason reason);
|
||||
predicate hasConstantBound(SemExpr e, D::Delta bound, boolean upper);
|
||||
|
||||
/**
|
||||
* Holds if `e >= bound + delta` (if `upper = false`) or `e <= bound + delta` (if `upper = true`).
|
||||
@@ -280,14 +249,6 @@ module RangeStage<
|
||||
DeltaSig D, BoundSig<D> Bounds, OverflowSig<D> OverflowParam, LangSig<D> LangParam,
|
||||
UtilSig<D> UtilParam>
|
||||
{
|
||||
class SemReason = LangParam::SemReason;
|
||||
|
||||
class SemCondReason = LangParam::SemCondReason;
|
||||
|
||||
class SemNoReason = LangParam::SemNoReason;
|
||||
|
||||
class SemTypeReason = LangParam::SemTypeReason;
|
||||
|
||||
private import Bounds
|
||||
private import LangParam
|
||||
private import UtilParam
|
||||
@@ -548,6 +509,36 @@ module RangeStage<
|
||||
)
|
||||
}
|
||||
|
||||
private newtype TSemReason =
|
||||
TSemNoReason() or
|
||||
TSemCondReason(SemGuard guard) { possibleReason(guard) }
|
||||
|
||||
/**
|
||||
* A reason for an inferred bound. This can either be `CondReason` if the bound
|
||||
* is due to a specific condition, or `NoReason` if the bound is inferred
|
||||
* without going through a bounding condition.
|
||||
*/
|
||||
abstract class SemReason extends TSemReason {
|
||||
/** Gets a textual representation of this reason. */
|
||||
abstract string toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* A reason for an inferred bound that indicates that the bound is inferred
|
||||
* without going through a bounding condition.
|
||||
*/
|
||||
class SemNoReason extends SemReason, TSemNoReason {
|
||||
override string toString() { result = "NoReason" }
|
||||
}
|
||||
|
||||
/** A reason for an inferred bound pointing to a condition. */
|
||||
class SemCondReason extends SemReason, TSemCondReason {
|
||||
/** Gets the condition that is the reason for the bound. */
|
||||
SemGuard getCond() { this = TSemCondReason(result) }
|
||||
|
||||
override string toString() { result = this.getCond().toString() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `e + delta` is a valid bound for `v` at `pos`.
|
||||
* - `upper = true` : `v <= e + delta`
|
||||
@@ -560,13 +551,13 @@ module RangeStage<
|
||||
semSsaUpdateStep(v, e, delta) and
|
||||
pos.hasReadOfVar(v) and
|
||||
(upper = true or upper = false) and
|
||||
reason instanceof SemNoReason
|
||||
reason = TSemNoReason()
|
||||
or
|
||||
exists(SemGuard guard, boolean testIsTrue |
|
||||
pos.hasReadOfVar(v) and
|
||||
guard = boundFlowCond(v, e, delta, upper, testIsTrue) and
|
||||
semGuardDirectlyControlsSsaRead(guard, pos, testIsTrue) and
|
||||
reason.(SemCondReason).getCond() = guard
|
||||
reason = TSemCondReason(guard)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -579,7 +570,7 @@ module RangeStage<
|
||||
pos.hasReadOfVar(v) and
|
||||
guard = semEqFlowCond(v, e, delta, false, testIsTrue) and
|
||||
semGuardDirectlyControlsSsaRead(guard, pos, testIsTrue) and
|
||||
reason.(SemCondReason).getCond() = guard
|
||||
reason = TSemCondReason(guard)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -709,7 +700,7 @@ module RangeStage<
|
||||
// upper = true: v <= mid + d1 <= b + d1 + d2 = b + delta
|
||||
// upper = false: v >= mid + d1 >= b + d1 + d2 = b + delta
|
||||
delta = D::fromFloat(D::toFloat(d1) + D::toFloat(d2)) and
|
||||
(if r1 instanceof SemNoReason then reason = r2 else reason = r1.combineWith(r2))
|
||||
(if r1 instanceof SemNoReason then reason = r2 else reason = r1)
|
||||
)
|
||||
or
|
||||
exists(D::Delta d, SemReason r1, SemReason r2 |
|
||||
@@ -723,9 +714,9 @@ module RangeStage<
|
||||
upper = false and delta = D::fromFloat(D::toFloat(d) + 1)
|
||||
) and
|
||||
(
|
||||
reason = r1.combineWith(r2)
|
||||
reason = r1
|
||||
or
|
||||
reason = r2.combineWith(r1) and not r2 instanceof SemNoReason
|
||||
reason = r2 and not r2 instanceof SemNoReason
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -795,7 +786,7 @@ module RangeStage<
|
||||
(upper = true or upper = false) and
|
||||
fromBackEdge0 = false and
|
||||
origdelta = D::fromFloat(0) and
|
||||
reason instanceof SemNoReason
|
||||
reason = TSemNoReason()
|
||||
|
|
||||
if semBackEdge(phi, inp, edge)
|
||||
then
|
||||
@@ -920,15 +911,14 @@ module RangeStage<
|
||||
* Holds if `e` has an upper (for `upper = true`) or lower
|
||||
* (for `upper = false`) bound of `b`.
|
||||
*/
|
||||
private predicate baseBound(SemExpr e, D::Delta b, boolean upper, SemReason reason) {
|
||||
hasConstantBound(e, b, upper, reason)
|
||||
private predicate baseBound(SemExpr e, D::Delta b, boolean upper) {
|
||||
hasConstantBound(e, b, upper)
|
||||
or
|
||||
upper = false and
|
||||
b = D::fromInt(0) and
|
||||
semPositive(e.(SemBitAndExpr).getAnOperand()) and
|
||||
// REVIEW: We let the language opt out here to preserve original results.
|
||||
not ignoreZeroLowerBound(e) and
|
||||
reason instanceof SemNoReason
|
||||
not ignoreZeroLowerBound(e)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1054,12 +1044,13 @@ module RangeStage<
|
||||
(upper = true or upper = false) and
|
||||
fromBackEdge = false and
|
||||
origdelta = delta and
|
||||
reason instanceof SemNoReason
|
||||
reason = TSemNoReason()
|
||||
or
|
||||
baseBound(e, delta, upper, reason) and
|
||||
baseBound(e, delta, upper) and
|
||||
b instanceof SemZeroBound and
|
||||
fromBackEdge = false and
|
||||
origdelta = delta
|
||||
origdelta = delta and
|
||||
reason = TSemNoReason()
|
||||
or
|
||||
exists(SemSsaVariable v, SemSsaReadPositionBlock bb |
|
||||
boundedSsa(v, bb, b, delta, upper, fromBackEdge, origdelta, reason) and
|
||||
@@ -1113,9 +1104,9 @@ module RangeStage<
|
||||
boundedConditionalExpr(cond, b, upper, true, d1, fbe1, od1, r1) and
|
||||
boundedConditionalExpr(cond, b, upper, false, d2, fbe2, od2, r2) and
|
||||
(
|
||||
delta = d1 and fromBackEdge = fbe1 and origdelta = od1 and reason = r1.combineWith(r2)
|
||||
delta = d1 and fromBackEdge = fbe1 and origdelta = od1 and reason = r1
|
||||
or
|
||||
delta = d2 and fromBackEdge = fbe2 and origdelta = od2 and reason = r2.combineWith(r1)
|
||||
delta = d2 and fromBackEdge = fbe2 and origdelta = od2 and reason = r2
|
||||
)
|
||||
|
|
||||
upper = true and delta = D::fromFloat(D::toFloat(d1).maximum(D::toFloat(d2)))
|
||||
@@ -1141,15 +1132,9 @@ module RangeStage<
|
||||
delta = D::fromFloat(D::toFloat(dLeft) + D::toFloat(dRight)) and
|
||||
fromBackEdge = fbeLeft.booleanOr(fbeRight)
|
||||
|
|
||||
b = bLeft and
|
||||
origdelta = odLeft and
|
||||
reason = rLeft.combineWith(rRight) and
|
||||
bRight instanceof SemZeroBound
|
||||
b = bLeft and origdelta = odLeft and reason = rLeft and bRight instanceof SemZeroBound
|
||||
or
|
||||
b = bRight and
|
||||
origdelta = odRight and
|
||||
reason = rRight.combineWith(rLeft) and
|
||||
bLeft instanceof SemZeroBound
|
||||
b = bRight and origdelta = odRight and reason = rRight and bLeft instanceof SemZeroBound
|
||||
)
|
||||
or
|
||||
exists(
|
||||
@@ -1165,9 +1150,9 @@ module RangeStage<
|
||||
(
|
||||
if D::toFloat(d1).abs() > D::toFloat(d2).abs()
|
||||
then (
|
||||
d_max = d1 and fromBackEdge = fbe1 and origdelta = od1 and reason = r1.combineWith(r2)
|
||||
d_max = d1 and fromBackEdge = fbe1 and origdelta = od1 and reason = r1
|
||||
) else (
|
||||
d_max = d2 and fromBackEdge = fbe2 and origdelta = od2 and reason = r2.combineWith(r1)
|
||||
d_max = d2 and fromBackEdge = fbe2 and origdelta = od2 and reason = r2
|
||||
)
|
||||
)
|
||||
|
|
||||
@@ -1183,14 +1168,11 @@ module RangeStage<
|
||||
boundedMulOperand(e, upper, true, dLeft, fbeLeft, odLeft, rLeft) and
|
||||
boundedMulOperand(e, upper, false, dRight, fbeRight, odRight, rRight) and
|
||||
delta = D::fromFloat(D::toFloat(dLeft) * D::toFloat(dRight)) and
|
||||
fromBackEdge = fbeLeft.booleanOr(fbeRight) and
|
||||
b instanceof SemZeroBound
|
||||
fromBackEdge = fbeLeft.booleanOr(fbeRight)
|
||||
|
|
||||
origdelta = odLeft and
|
||||
reason = rLeft.combineWith(rRight)
|
||||
b instanceof SemZeroBound and origdelta = odLeft and reason = rLeft
|
||||
or
|
||||
origdelta = odRight and
|
||||
reason = rRight.combineWith(rLeft)
|
||||
b instanceof SemZeroBound and origdelta = odRight and reason = rRight
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
/**
|
||||
* Provides a `Make` parameterized module for constructing a `Reason` type that is used
|
||||
* when implementing the `LangSig` module.
|
||||
*/
|
||||
|
||||
private import semmle.code.cpp.rangeanalysis.new.internal.semantic.Semantic
|
||||
|
||||
/** The necessary parameters that must be implemented to instantiate `Make`. */
|
||||
signature module ParamSig {
|
||||
class TypeReasonImpl;
|
||||
}
|
||||
|
||||
/**
|
||||
* The module that constructs a `Reason` type when provided with an implementation
|
||||
* of `ParamSig`.
|
||||
*/
|
||||
module Make<ParamSig Param> {
|
||||
private import Param
|
||||
|
||||
private newtype TSemReason =
|
||||
TSemNoReason() or
|
||||
TSemCondReason(SemGuard guard) or
|
||||
TSemTypeReason(TypeReasonImpl trc)
|
||||
|
||||
/**
|
||||
* A reason for an inferred bound. This can either be `CondReason` if the bound
|
||||
* is due to a specific condition, or `NoReason` if the bound is inferred
|
||||
* without going through a bounding condition.
|
||||
*/
|
||||
abstract class SemReason extends TSemReason {
|
||||
/** Gets a textual representation of this reason. */
|
||||
abstract string toString();
|
||||
|
||||
bindingset[this, reason]
|
||||
abstract SemReason combineWith(SemReason reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* A reason for an inferred bound that indicates that the bound is inferred
|
||||
* without going through a bounding condition.
|
||||
*/
|
||||
class SemNoReason extends SemReason, TSemNoReason {
|
||||
override string toString() { result = "NoReason" }
|
||||
|
||||
override SemReason combineWith(SemReason reason) { result = reason }
|
||||
}
|
||||
|
||||
/** A reason for an inferred bound pointing to a condition. */
|
||||
class SemCondReason extends SemReason, TSemCondReason {
|
||||
/** Gets the condition that is the reason for the bound. */
|
||||
SemGuard getCond() { this = TSemCondReason(result) }
|
||||
|
||||
override string toString() { result = this.getCond().toString() }
|
||||
|
||||
bindingset[this, reason]
|
||||
override SemReason combineWith(SemReason reason) {
|
||||
// Since we end up reporting a `SemReason` for the inferred bound we often pick somewhat
|
||||
// arbitrarily between two `SemReason`s during the analysis. This isn't an issue for most reasons
|
||||
// since they're mainly used for constructing alert messages. However, the `SemTypeReason` is
|
||||
// supposed to be used in query logic to filter out bounds inferred by type-based analysis if
|
||||
// the query author chooses to do so. So we need to ensure that if _any_ of the bounds that
|
||||
// contribute to the final bound depends on type information then the `SemReason` we report must
|
||||
// be a `SemTypeReason`. So when we need to combine this `SemCondReason` with a `SemTypeReason`
|
||||
// the result should always be a `SemTypeReason`.
|
||||
if reason instanceof SemTypeReason then result instanceof SemTypeReason else result = this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A reason for an inferred bound that indicates that the bound is inferred
|
||||
* based on type-information.
|
||||
*/
|
||||
class SemTypeReason extends SemReason, TSemTypeReason {
|
||||
TypeReasonImpl impl;
|
||||
|
||||
SemTypeReason() { this = TSemTypeReason(impl) }
|
||||
|
||||
override string toString() { result = "TypeReason" }
|
||||
|
||||
bindingset[this, reason]
|
||||
override SemReason combineWith(SemReason reason) { result = this and exists(reason) }
|
||||
}
|
||||
}
|
||||
@@ -96,7 +96,7 @@ predicate hasSize(HeuristicAllocationExpr alloc, DataFlow::Node n, int state) {
|
||||
* but because there's a strict comparison that compares `n` against the size of the allocation this
|
||||
* snippet is fine.
|
||||
*/
|
||||
module SizeBarrier {
|
||||
private module SizeBarrier {
|
||||
private module SizeBarrierConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) {
|
||||
// The sources is the same as in the sources for the second
|
||||
@@ -104,35 +104,60 @@ module SizeBarrier {
|
||||
hasSize(_, source, _)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `small <= large + k` holds if `g` evaluates to `testIsTrue`.
|
||||
*/
|
||||
additional predicate isSink(
|
||||
DataFlow::Node left, DataFlow::Node right, IRGuardCondition g, int k, boolean testIsTrue
|
||||
DataFlow::Node small, DataFlow::Node large, IRGuardCondition g, int k, boolean testIsTrue
|
||||
) {
|
||||
// The sink is any "large" side of a relational comparison. i.e., the `right` expression
|
||||
// in a guard such as `left < right + k`.
|
||||
g.comparesLt(left.asOperand(), right.asOperand(), k, true, testIsTrue)
|
||||
// The sink is any "large" side of a relational comparison. i.e., the `large` expression
|
||||
// in a guard such as `small <= large + k`.
|
||||
g.comparesLt(small.asOperand(), large.asOperand(), k + 1, true, testIsTrue)
|
||||
}
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { isSink(_, sink, _, _, _) }
|
||||
}
|
||||
|
||||
private import DataFlow::Global<SizeBarrierConfig>
|
||||
module SizeBarrierFlow = DataFlow::Global<SizeBarrierConfig>;
|
||||
|
||||
private int getAFlowStateForNode(DataFlow::Node node) {
|
||||
private int getASizeAddend(DataFlow::Node node) {
|
||||
exists(DataFlow::Node source |
|
||||
flow(source, node) and
|
||||
SizeBarrierFlow::flow(source, node) and
|
||||
hasSize(_, source, result)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `small <= large + k` holds if `g` evaluates to `edge`.
|
||||
*/
|
||||
private predicate operandGuardChecks(
|
||||
IRGuardCondition g, Operand left, Operand right, int state, boolean edge
|
||||
IRGuardCondition g, Operand small, DataFlow::Node large, int k, boolean edge
|
||||
) {
|
||||
exists(DataFlow::Node nLeft, DataFlow::Node nRight, int k |
|
||||
nRight.asOperand() = right and
|
||||
nLeft.asOperand() = left and
|
||||
SizeBarrierConfig::isSink(nLeft, nRight, g, k, edge) and
|
||||
state = getAFlowStateForNode(nRight) and
|
||||
k <= state
|
||||
SizeBarrierFlow::flowTo(large) and
|
||||
SizeBarrierConfig::isSink(DataFlow::operandNode(small), large, g, k, edge)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an instruction `instr` that is guarded by a check such as `instr <= small + delta` where
|
||||
* `small <= _ + k` and `small` is the "small side" of of a relational comparison that checks
|
||||
* whether `small <= size` where `size` is the size of an allocation.
|
||||
*/
|
||||
Instruction getABarrierInstruction0(int delta, int k) {
|
||||
exists(
|
||||
IRGuardCondition g, ValueNumber value, Operand small, boolean edge, DataFlow::Node large
|
||||
|
|
||||
// We know:
|
||||
// 1. result <= value + delta (by `bounded`)
|
||||
// 2. value <= large + k (by `operandGuardChecks`).
|
||||
// So:
|
||||
// result <= value + delta (by 1.)
|
||||
// <= large + k + delta (by 2.)
|
||||
small = value.getAUse() and
|
||||
operandGuardChecks(pragma[only_bind_into](g), pragma[only_bind_into](small), large,
|
||||
pragma[only_bind_into](k), pragma[only_bind_into](edge)) and
|
||||
bounded(result, value.getAnInstruction(), delta) and
|
||||
g.controls(result.getBlock(), edge) and
|
||||
k < getASizeAddend(large)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -140,13 +165,14 @@ module SizeBarrier {
|
||||
* Gets an instruction that is guarded by a guard condition which ensures that
|
||||
* the value of the instruction is upper-bounded by size of some allocation.
|
||||
*/
|
||||
bindingset[state]
|
||||
pragma[inline_late]
|
||||
Instruction getABarrierInstruction(int state) {
|
||||
exists(IRGuardCondition g, ValueNumber value, Operand use, boolean edge |
|
||||
use = value.getAUse() and
|
||||
operandGuardChecks(pragma[only_bind_into](g), pragma[only_bind_into](use), _,
|
||||
pragma[only_bind_into](state), pragma[only_bind_into](edge)) and
|
||||
result = value.getAnInstruction() and
|
||||
g.controls(result.getBlock(), edge)
|
||||
exists(int delta, int k |
|
||||
state > k + delta and
|
||||
// result <= "size of allocation" + delta + k
|
||||
// < "size of allocation" + state
|
||||
result = getABarrierInstruction0(delta, k)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -155,14 +181,16 @@ module SizeBarrier {
|
||||
* the value of the node is upper-bounded by size of some allocation.
|
||||
*/
|
||||
DataFlow::Node getABarrierNode(int state) {
|
||||
result.asOperand() = getABarrierInstruction(state).getAUse()
|
||||
exists(DataFlow::Node source, int delta, int k |
|
||||
SizeBarrierFlow::flow(source, result) and
|
||||
hasSize(_, source, state) and
|
||||
result.asInstruction() = SizeBarrier::getABarrierInstruction0(delta, k) and
|
||||
state > k + delta
|
||||
// so now we have:
|
||||
// result <= "size of allocation" + delta + k
|
||||
// < "size of allocation" + state
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the block of a node that is guarded (see `getABarrierInstruction` or
|
||||
* `getABarrierNode` for the definition of what it means to be guarded).
|
||||
*/
|
||||
IRBlock getABarrierBlock(int state) { result.getAnInstruction() = getABarrierInstruction(state) }
|
||||
}
|
||||
|
||||
private module InterestingPointerAddInstruction {
|
||||
|
||||
@@ -66,11 +66,14 @@
|
||||
* module. Since the node we are tracking is not necessarily _equal_ to the pointer-arithmetic instruction, but rather satisfies
|
||||
* `node.asInstruction() <= pai + deltaDerefSourceAndPai`, we need to account for the delta when checking if a guard is sufficiently
|
||||
* strong to infer that a future dereference is safe. To do this, we check that the guard guarantees that a node `n` satisfies
|
||||
* `n < node + k` where `node` is a node we know is equal to the value of the dereference source (i.e., it satisfies
|
||||
* `node.asInstruction() <= pai + deltaDerefSourceAndPai`) and `k <= deltaDerefSourceAndPai`. Combining this we have
|
||||
* `n < node + k <= node + deltaDerefSourceAndPai <= pai + 2*deltaDerefSourceAndPai` (TODO: Oops. This math doesn't quite work out.
|
||||
* I think this is because we need to redefine the `BarrierConfig` to start flow at the pointer-arithmetic instruction instead of
|
||||
* at the dereference source. When combined with TODO above it's easy to show that this guard ensures that the dereference is safe).
|
||||
* `n < node + k` where `node` is a node such that `node <= pai`. Thus, we know that any node `m` such that `m <= n + delta` where
|
||||
* `delta + k <= 0` will be safe because:
|
||||
* ```
|
||||
* m <= n + delta
|
||||
* < node + k + delta
|
||||
* <= pai + k + delta
|
||||
* <= pai
|
||||
* ```
|
||||
*/
|
||||
|
||||
private import cpp
|
||||
@@ -82,16 +85,19 @@ private import RangeAnalysisUtil
|
||||
|
||||
private module InvalidPointerToDerefBarrier {
|
||||
private module BarrierConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) {
|
||||
// The sources is the same as in the sources for `InvalidPointerToDerefConfig`.
|
||||
invalidPointerToDerefSource(_, _, source, _)
|
||||
additional predicate isSource(DataFlow::Node source, PointerArithmeticInstruction pai) {
|
||||
invalidPointerToDerefSource(_, pai, _, _) and
|
||||
// source <= pai
|
||||
bounded2(source.asInstruction(), pai, any(int d | d <= 0))
|
||||
}
|
||||
|
||||
predicate isSource(DataFlow::Node source) { isSource(source, _) }
|
||||
|
||||
additional predicate isSink(
|
||||
DataFlow::Node left, DataFlow::Node right, IRGuardCondition g, int k, boolean testIsTrue
|
||||
DataFlow::Node small, DataFlow::Node large, IRGuardCondition g, int k, boolean testIsTrue
|
||||
) {
|
||||
// The sink is any "large" side of a relational comparison.
|
||||
g.comparesLt(left.asOperand(), right.asOperand(), k, true, testIsTrue)
|
||||
g.comparesLt(small.asOperand(), large.asOperand(), k, true, testIsTrue)
|
||||
}
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { isSink(_, sink, _, _, _) }
|
||||
@@ -99,59 +105,82 @@ private module InvalidPointerToDerefBarrier {
|
||||
|
||||
private module BarrierFlow = DataFlow::Global<BarrierConfig>;
|
||||
|
||||
private int getInvalidPointerToDerefSourceDelta(DataFlow::Node node) {
|
||||
exists(DataFlow::Node source |
|
||||
BarrierFlow::flow(source, node) and
|
||||
invalidPointerToDerefSource(_, _, source, result)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `g` ensures that `small < large + k` if `g` evaluates to `edge`.
|
||||
*
|
||||
* Additionally, it also holds that `large <= pai`. Thus, when `g` evaluates to `edge`
|
||||
* it holds that `small < pai + k`.
|
||||
*/
|
||||
private predicate operandGuardChecks(
|
||||
IRGuardCondition g, Operand left, Operand right, int state, boolean edge
|
||||
PointerArithmeticInstruction pai, IRGuardCondition g, Operand small, int k, boolean edge
|
||||
) {
|
||||
exists(DataFlow::Node nLeft, DataFlow::Node nRight, int k |
|
||||
nRight.asOperand() = right and
|
||||
nLeft.asOperand() = left and
|
||||
BarrierConfig::isSink(nLeft, nRight, g, k, edge) and
|
||||
state = getInvalidPointerToDerefSourceDelta(nRight) and
|
||||
k <= state
|
||||
exists(DataFlow::Node source, DataFlow::Node nSmall, DataFlow::Node nLarge |
|
||||
nSmall.asOperand() = small and
|
||||
BarrierConfig::isSource(source, pai) and
|
||||
BarrierFlow::flow(source, nLarge) and
|
||||
BarrierConfig::isSink(nSmall, nLarge, g, k, edge)
|
||||
)
|
||||
}
|
||||
|
||||
Instruction getABarrierInstruction(int state) {
|
||||
exists(IRGuardCondition g, ValueNumber value, Operand use, boolean edge |
|
||||
/**
|
||||
* Gets an instruction `instr` such that `instr < pai`.
|
||||
*/
|
||||
Instruction getABarrierInstruction(PointerArithmeticInstruction pai) {
|
||||
exists(IRGuardCondition g, ValueNumber value, Operand use, boolean edge, int delta, int k |
|
||||
use = value.getAUse() and
|
||||
operandGuardChecks(pragma[only_bind_into](g), pragma[only_bind_into](use), _, state,
|
||||
pragma[only_bind_into](edge)) and
|
||||
result = value.getAnInstruction() and
|
||||
g.controls(result.getBlock(), edge)
|
||||
// value < pai + k
|
||||
operandGuardChecks(pai, pragma[only_bind_into](g), pragma[only_bind_into](use),
|
||||
pragma[only_bind_into](k), pragma[only_bind_into](edge)) and
|
||||
// result <= value + delta
|
||||
bounded(result, value.getAnInstruction(), delta) and
|
||||
g.controls(result.getBlock(), edge) and
|
||||
delta + k <= 0
|
||||
// combining the above we have: result < pai + k + delta <= pai
|
||||
)
|
||||
}
|
||||
|
||||
DataFlow::Node getABarrierNode() { result.asOperand() = getABarrierInstruction(_).getAUse() }
|
||||
DataFlow::Node getABarrierNode(PointerArithmeticInstruction pai) {
|
||||
result.asOperand() = getABarrierInstruction(pai).getAUse()
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
IRBlock getABarrierBlock(int state) { result.getAnInstruction() = getABarrierInstruction(state) }
|
||||
/**
|
||||
* Gets an address operand whose definition `instr` satisfies `instr < pai`.
|
||||
*/
|
||||
AddressOperand getABarrierAddressOperand(PointerArithmeticInstruction pai) {
|
||||
result.getDef() = getABarrierInstruction(pai)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A configuration to track flow from a pointer-arithmetic operation found
|
||||
* by `AllocToInvalidPointerConfig` to a dereference of the pointer.
|
||||
*/
|
||||
private module InvalidPointerToDerefConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { invalidPointerToDerefSource(_, _, source, _) }
|
||||
private module InvalidPointerToDerefConfig implements DataFlow::StateConfigSig {
|
||||
class FlowState extends PointerArithmeticInstruction {
|
||||
FlowState() { invalidPointerToDerefSource(_, this, _, _) }
|
||||
}
|
||||
|
||||
predicate isSource(DataFlow::Node source, FlowState pai) {
|
||||
invalidPointerToDerefSource(_, pai, source, _)
|
||||
}
|
||||
|
||||
pragma[inline]
|
||||
predicate isSink(DataFlow::Node sink) { isInvalidPointerDerefSink(sink, _, _, _) }
|
||||
predicate isSink(DataFlow::Node sink) { isInvalidPointerDerefSink(sink, _, _, _, _) }
|
||||
|
||||
predicate isSink(DataFlow::Node sink, FlowState pai) { none() }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) {
|
||||
node = any(DataFlow::SsaPhiNode phi | not phi.isPhiRead()).getAnInput(true)
|
||||
or
|
||||
node = InvalidPointerToDerefBarrier::getABarrierNode()
|
||||
}
|
||||
|
||||
predicate isBarrier(DataFlow::Node node, FlowState pai) {
|
||||
// `node = getABarrierNode(pai)` ensures that node < pai, so this node is safe to dereference.
|
||||
// Note that this is the only place where the `FlowState` is used in this configuration.
|
||||
node = InvalidPointerToDerefBarrier::getABarrierNode(pai)
|
||||
}
|
||||
}
|
||||
|
||||
private import DataFlow::Global<InvalidPointerToDerefConfig>
|
||||
private import DataFlow::GlobalWithState<InvalidPointerToDerefConfig>
|
||||
|
||||
/**
|
||||
* Holds if `allocSource` is dataflow node that represents an allocation that flows to the
|
||||
@@ -165,19 +194,14 @@ private predicate invalidPointerToDerefSource(
|
||||
DataFlow::Node allocSource, PointerArithmeticInstruction pai, DataFlow::Node derefSource,
|
||||
int deltaDerefSourceAndPai
|
||||
) {
|
||||
exists(int rhsSizeDelta |
|
||||
// Note that `deltaDerefSourceAndPai` is not necessarily equal to `rhsSizeDelta`:
|
||||
// `rhsSizeDelta` is the constant offset added to the size of the allocation, and
|
||||
// `deltaDerefSourceAndPai` is the constant difference between the pointer-arithmetic instruction
|
||||
// and the instruction computing the address for which we will search for a dereference.
|
||||
AllocToInvalidPointer::pointerAddInstructionHasBounds(allocSource, pai, _, rhsSizeDelta) and
|
||||
bounded2(derefSource.asInstruction(), pai, deltaDerefSourceAndPai) and
|
||||
deltaDerefSourceAndPai >= 0 and
|
||||
// TODO: This condition will go away once #13725 is merged, and then we can make `SizeBarrier`
|
||||
// private to `AllocationToInvalidPointer.qll`.
|
||||
not derefSource.getBasicBlock() =
|
||||
AllocToInvalidPointer::SizeBarrier::getABarrierBlock(rhsSizeDelta)
|
||||
)
|
||||
// Note that `deltaDerefSourceAndPai` is not necessarily equal to `rhsSizeDelta`:
|
||||
// `rhsSizeDelta` is the constant offset added to the size of the allocation, and
|
||||
// `deltaDerefSourceAndPai` is the constant difference between the pointer-arithmetic instruction
|
||||
// and the instruction computing the address for which we will search for a dereference.
|
||||
AllocToInvalidPointer::pointerAddInstructionHasBounds(allocSource, pai, _, _) and
|
||||
// derefSource <= pai + deltaDerefSourceAndPai
|
||||
bounded2(derefSource.asInstruction(), pai, deltaDerefSourceAndPai) and
|
||||
deltaDerefSourceAndPai >= 0
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -187,15 +211,14 @@ private predicate invalidPointerToDerefSource(
|
||||
*/
|
||||
pragma[inline]
|
||||
private predicate isInvalidPointerDerefSink(
|
||||
DataFlow::Node sink, Instruction i, string operation, int deltaDerefSinkAndDerefAddress
|
||||
DataFlow::Node sink, AddressOperand addr, Instruction i, string operation,
|
||||
int deltaDerefSinkAndDerefAddress
|
||||
) {
|
||||
exists(AddressOperand addr, Instruction s, IRBlock b |
|
||||
exists(Instruction s |
|
||||
s = sink.asInstruction() and
|
||||
bounded(addr.getDef(), s, deltaDerefSinkAndDerefAddress) and
|
||||
deltaDerefSinkAndDerefAddress >= 0 and
|
||||
i.getAnOperand() = addr and
|
||||
b = i.getBlock() and
|
||||
not b = InvalidPointerToDerefBarrier::getABarrierBlock(deltaDerefSinkAndDerefAddress)
|
||||
i.getAnOperand() = addr
|
||||
|
|
||||
i instanceof StoreInstruction and
|
||||
operation = "write"
|
||||
@@ -221,9 +244,11 @@ private Instruction getASuccessor(Instruction instr) {
|
||||
instr.getBlock().getASuccessor+() = result.getBlock()
|
||||
}
|
||||
|
||||
private predicate paiForDereferenceSink(PointerArithmeticInstruction pai, DataFlow::Node derefSink) {
|
||||
private predicate paiForDereferenceSink(
|
||||
PointerArithmeticInstruction pai, DataFlow::Node derefSink, int deltaDerefSourceAndPai
|
||||
) {
|
||||
exists(DataFlow::Node derefSource |
|
||||
invalidPointerToDerefSource(_, pai, derefSource, _) and
|
||||
invalidPointerToDerefSource(_, pai, derefSource, deltaDerefSourceAndPai) and
|
||||
flow(derefSource, derefSink)
|
||||
)
|
||||
}
|
||||
@@ -235,13 +260,15 @@ private predicate paiForDereferenceSink(PointerArithmeticInstruction pai, DataFl
|
||||
*/
|
||||
private predicate derefSinkToOperation(
|
||||
DataFlow::Node derefSink, PointerArithmeticInstruction pai, DataFlow::Node operation,
|
||||
string description, int deltaDerefSinkAndDerefAddress
|
||||
string description, int deltaDerefSourceAndPai, int deltaDerefSinkAndDerefAddress
|
||||
) {
|
||||
exists(Instruction operationInstr |
|
||||
paiForDereferenceSink(pai, pragma[only_bind_into](derefSink)) and
|
||||
isInvalidPointerDerefSink(derefSink, operationInstr, description, deltaDerefSinkAndDerefAddress) and
|
||||
exists(Instruction operationInstr, AddressOperand addr |
|
||||
paiForDereferenceSink(pai, pragma[only_bind_into](derefSink), deltaDerefSourceAndPai) and
|
||||
isInvalidPointerDerefSink(derefSink, addr, operationInstr, description,
|
||||
deltaDerefSinkAndDerefAddress) and
|
||||
operationInstr = getASuccessor(derefSink.asInstruction()) and
|
||||
operation.asInstruction() = operationInstr
|
||||
operation.asInstruction() = operationInstr and
|
||||
not addr = InvalidPointerToDerefBarrier::getABarrierAddressOperand(pai)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -260,7 +287,8 @@ predicate operationIsOffBy(
|
||||
exists(int deltaDerefSourceAndPai, int deltaDerefSinkAndDerefAddress |
|
||||
invalidPointerToDerefSource(allocation, pai, derefSource, deltaDerefSourceAndPai) and
|
||||
flow(derefSource, derefSink) and
|
||||
derefSinkToOperation(derefSink, pai, operation, description, deltaDerefSinkAndDerefAddress) and
|
||||
derefSinkToOperation(derefSink, pai, operation, description, deltaDerefSourceAndPai,
|
||||
deltaDerefSinkAndDerefAddress) and
|
||||
delta = deltaDerefSourceAndPai + deltaDerefSinkAndDerefAddress
|
||||
)
|
||||
}
|
||||
|
||||
@@ -18,15 +18,23 @@ private Instruction getABoundIn(SemBound b, IRFunction func) {
|
||||
* Holds if `i <= b + delta`.
|
||||
*/
|
||||
pragma[inline]
|
||||
private predicate boundedImpl(Instruction i, Instruction b, int delta) {
|
||||
private predicate boundedImplCand(Instruction i, Instruction b, int delta) {
|
||||
exists(SemBound bound, IRFunction func |
|
||||
semBounded(getSemanticExpr(i), bound, delta, true,
|
||||
any(SemReason reason | not reason instanceof SemTypeReason)) and
|
||||
semBounded(getSemanticExpr(i), bound, delta, true, _) and
|
||||
b = getABoundIn(bound, func) and
|
||||
i.getEnclosingIRFunction() = func
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `i <= b + delta` and `delta` is the smallest integer that satisfies
|
||||
* this condition.
|
||||
*/
|
||||
pragma[inline]
|
||||
private predicate boundedImpl(Instruction i, Instruction b, int delta) {
|
||||
delta = min(int cand | boundedImplCand(i, b, cand))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `i <= b + delta`.
|
||||
*
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
## 0.7.2
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.7.1
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
@@ -24,7 +24,7 @@ import semmle.code.cpp.security.BufferWrite
|
||||
from BufferWrite bw, int destSize
|
||||
where
|
||||
bw.hasExplicitLimit() and // has an explicit size limit
|
||||
destSize = getBufferSize(bw.getDest(), _) and
|
||||
destSize = max(getBufferSize(bw.getDest(), _)) and
|
||||
bw.getExplicitLimit() > destSize // but it's larger than the destination
|
||||
select bw,
|
||||
"This '" + bw.getBWDesc() + "' operation is limited to " + bw.getExplicitLimit() +
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The `cpp/badly-bounded-write` query could report false positives when a pointer was first initialized with a literal and later assigned a dynamically allocated array. These false positives now no longer occur.
|
||||
3
cpp/ql/src/change-notes/released/0.7.2.md
Normal file
3
cpp/ql/src/change-notes/released/0.7.2.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.7.2
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.7.1
|
||||
lastReleaseVersion: 0.7.2
|
||||
|
||||
@@ -28,8 +28,7 @@ Instruction getABoundIn(SemBound b, IRFunction func) {
|
||||
pragma[inline]
|
||||
predicate boundedImpl(Instruction i, Instruction b, int delta) {
|
||||
exists(SemBound bound, IRFunction func |
|
||||
semBounded(getSemanticExpr(i), bound, delta, true,
|
||||
any(SemReason reason | not reason instanceof SemTypeReason)) and
|
||||
semBounded(getSemanticExpr(i), bound, delta, true, _) and
|
||||
b = getABoundIn(bound, func) and
|
||||
pragma[only_bind_out](i.getEnclosingIRFunction()) = func
|
||||
)
|
||||
@@ -94,8 +93,7 @@ predicate arrayTypeHasSizes(ArrayType arr, int baseTypeSize, int size) {
|
||||
bindingset[pai]
|
||||
pragma[inline_late]
|
||||
predicate constantUpperBounded(PointerArithmeticInstruction pai, int delta) {
|
||||
semBounded(getSemanticExpr(pai.getRight()), any(SemZeroBound b), delta, true,
|
||||
any(SemReason reason | not reason instanceof SemTypeReason))
|
||||
semBounded(getSemanticExpr(pai.getRight()), any(SemZeroBound b), delta, true, _)
|
||||
}
|
||||
|
||||
bindingset[pai, size]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/cpp-queries
|
||||
version: 0.7.2-dev
|
||||
version: 0.7.3-dev
|
||||
groups:
|
||||
- cpp
|
||||
- queries
|
||||
|
||||
@@ -129,7 +129,6 @@ edges
|
||||
| test.cpp:271:14:271:21 | ... + ... | test.cpp:271:14:271:21 | ... + ... |
|
||||
| test.cpp:271:14:271:21 | ... + ... | test.cpp:274:5:274:10 | ... = ... |
|
||||
| test.cpp:271:14:271:21 | ... + ... | test.cpp:274:5:274:10 | ... = ... |
|
||||
| test.cpp:304:15:304:26 | new[] | test.cpp:308:5:308:29 | ... = ... |
|
||||
| test.cpp:355:14:355:27 | new[] | test.cpp:356:15:356:23 | ... + ... |
|
||||
| test.cpp:355:14:355:27 | new[] | test.cpp:356:15:356:23 | ... + ... |
|
||||
| test.cpp:355:14:355:27 | new[] | test.cpp:357:24:357:30 | ... + ... |
|
||||
@@ -214,20 +213,21 @@ edges
|
||||
| test.cpp:543:14:543:27 | new[] | test.cpp:548:5:548:19 | ... = ... |
|
||||
| test.cpp:554:14:554:27 | new[] | test.cpp:559:5:559:19 | ... = ... |
|
||||
| test.cpp:642:14:642:31 | new[] | test.cpp:647:5:647:19 | ... = ... |
|
||||
| test.cpp:652:14:652:27 | new[] | test.cpp:656:3:656:6 | ... ++ |
|
||||
| test.cpp:652:14:652:27 | new[] | test.cpp:656:3:656:6 | ... ++ |
|
||||
| test.cpp:652:14:652:27 | new[] | test.cpp:662:3:662:11 | ... = ... |
|
||||
| test.cpp:656:3:656:6 | ... ++ | test.cpp:656:3:656:6 | ... ++ |
|
||||
| test.cpp:656:3:656:6 | ... ++ | test.cpp:662:3:662:11 | ... = ... |
|
||||
| test.cpp:656:3:656:6 | ... ++ | test.cpp:662:3:662:11 | ... = ... |
|
||||
| test.cpp:667:14:667:31 | new[] | test.cpp:675:7:675:23 | ... = ... |
|
||||
| test.cpp:695:13:695:26 | new[] | test.cpp:698:5:698:10 | ... += ... |
|
||||
| test.cpp:695:13:695:26 | new[] | test.cpp:698:5:698:10 | ... += ... |
|
||||
| test.cpp:698:5:698:10 | ... += ... | test.cpp:698:5:698:10 | ... += ... |
|
||||
| test.cpp:698:5:698:10 | ... += ... | test.cpp:701:15:701:16 | * ... |
|
||||
| test.cpp:705:18:705:18 | q | test.cpp:705:18:705:18 | q |
|
||||
| test.cpp:705:18:705:18 | q | test.cpp:706:12:706:13 | * ... |
|
||||
| test.cpp:705:18:705:18 | q | test.cpp:706:12:706:13 | * ... |
|
||||
| test.cpp:711:13:711:26 | new[] | test.cpp:714:11:714:11 | q |
|
||||
| test.cpp:714:11:714:11 | q | test.cpp:705:18:705:18 | q |
|
||||
| test.cpp:730:12:730:28 | new[] | test.cpp:732:16:732:26 | ... + ... |
|
||||
| test.cpp:730:12:730:28 | new[] | test.cpp:732:16:732:26 | ... + ... |
|
||||
| test.cpp:730:12:730:28 | new[] | test.cpp:733:5:733:12 | ... = ... |
|
||||
| test.cpp:732:16:732:26 | ... + ... | test.cpp:732:16:732:26 | ... + ... |
|
||||
| test.cpp:732:16:732:26 | ... + ... | test.cpp:733:5:733:12 | ... = ... |
|
||||
| test.cpp:732:16:732:26 | ... + ... | test.cpp:733:5:733:12 | ... = ... |
|
||||
nodes
|
||||
| test.cpp:4:15:4:20 | call to malloc | semmle.label | call to malloc |
|
||||
| test.cpp:5:15:5:22 | ... + ... | semmle.label | ... + ... |
|
||||
@@ -320,8 +320,6 @@ nodes
|
||||
| test.cpp:271:14:271:21 | ... + ... | semmle.label | ... + ... |
|
||||
| test.cpp:271:14:271:21 | ... + ... | semmle.label | ... + ... |
|
||||
| test.cpp:274:5:274:10 | ... = ... | semmle.label | ... = ... |
|
||||
| test.cpp:304:15:304:26 | new[] | semmle.label | new[] |
|
||||
| test.cpp:308:5:308:29 | ... = ... | semmle.label | ... = ... |
|
||||
| test.cpp:355:14:355:27 | new[] | semmle.label | new[] |
|
||||
| test.cpp:356:15:356:23 | ... + ... | semmle.label | ... + ... |
|
||||
| test.cpp:356:15:356:23 | ... + ... | semmle.label | ... + ... |
|
||||
@@ -371,20 +369,19 @@ nodes
|
||||
| test.cpp:559:5:559:19 | ... = ... | semmle.label | ... = ... |
|
||||
| test.cpp:642:14:642:31 | new[] | semmle.label | new[] |
|
||||
| test.cpp:647:5:647:19 | ... = ... | semmle.label | ... = ... |
|
||||
| test.cpp:652:14:652:27 | new[] | semmle.label | new[] |
|
||||
| test.cpp:656:3:656:6 | ... ++ | semmle.label | ... ++ |
|
||||
| test.cpp:656:3:656:6 | ... ++ | semmle.label | ... ++ |
|
||||
| test.cpp:662:3:662:11 | ... = ... | semmle.label | ... = ... |
|
||||
| test.cpp:667:14:667:31 | new[] | semmle.label | new[] |
|
||||
| test.cpp:675:7:675:23 | ... = ... | semmle.label | ... = ... |
|
||||
| test.cpp:695:13:695:26 | new[] | semmle.label | new[] |
|
||||
| test.cpp:698:5:698:10 | ... += ... | semmle.label | ... += ... |
|
||||
| test.cpp:698:5:698:10 | ... += ... | semmle.label | ... += ... |
|
||||
| test.cpp:701:15:701:16 | * ... | semmle.label | * ... |
|
||||
| test.cpp:705:18:705:18 | q | semmle.label | q |
|
||||
| test.cpp:705:18:705:18 | q | semmle.label | q |
|
||||
| test.cpp:706:12:706:13 | * ... | semmle.label | * ... |
|
||||
| test.cpp:711:13:711:26 | new[] | semmle.label | new[] |
|
||||
| test.cpp:714:11:714:11 | q | semmle.label | q |
|
||||
| test.cpp:730:12:730:28 | new[] | semmle.label | new[] |
|
||||
| test.cpp:732:16:732:26 | ... + ... | semmle.label | ... + ... |
|
||||
| test.cpp:732:16:732:26 | ... + ... | semmle.label | ... + ... |
|
||||
| test.cpp:733:5:733:12 | ... = ... | semmle.label | ... = ... |
|
||||
subpaths
|
||||
#select
|
||||
| test.cpp:6:14:6:15 | * ... | test.cpp:4:15:4:20 | call to malloc | test.cpp:6:14:6:15 | * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:4:15:4:20 | call to malloc | call to malloc | test.cpp:5:19:5:22 | size | size |
|
||||
@@ -406,7 +403,6 @@ subpaths
|
||||
| test.cpp:254:9:254:16 | ... = ... | test.cpp:248:24:248:30 | call to realloc | test.cpp:254:9:254:16 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:248:24:248:30 | call to realloc | call to realloc | test.cpp:254:11:254:11 | i | i |
|
||||
| test.cpp:264:13:264:14 | * ... | test.cpp:260:13:260:24 | new[] | test.cpp:264:13:264:14 | * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:260:13:260:24 | new[] | new[] | test.cpp:261:19:261:21 | len | len |
|
||||
| test.cpp:274:5:274:10 | ... = ... | test.cpp:270:13:270:24 | new[] | test.cpp:274:5:274:10 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:270:13:270:24 | new[] | new[] | test.cpp:271:19:271:21 | len | len |
|
||||
| test.cpp:308:5:308:29 | ... = ... | test.cpp:304:15:304:26 | new[] | test.cpp:308:5:308:29 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:304:15:304:26 | new[] | new[] | test.cpp:308:8:308:10 | ... + ... | ... + ... |
|
||||
| test.cpp:358:14:358:26 | * ... | test.cpp:355:14:355:27 | new[] | test.cpp:358:14:358:26 | * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@ + 1. | test.cpp:355:14:355:27 | new[] | new[] | test.cpp:356:20:356:23 | size | size |
|
||||
| test.cpp:359:14:359:32 | * ... | test.cpp:355:14:355:27 | new[] | test.cpp:359:14:359:32 | * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@ + 2. | test.cpp:355:14:355:27 | new[] | new[] | test.cpp:356:20:356:23 | size | size |
|
||||
| test.cpp:384:13:384:16 | * ... | test.cpp:377:14:377:27 | new[] | test.cpp:384:13:384:16 | * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:377:14:377:27 | new[] | new[] | test.cpp:378:20:378:23 | size | size |
|
||||
@@ -418,7 +414,6 @@ subpaths
|
||||
| test.cpp:548:5:548:19 | ... = ... | test.cpp:543:14:543:27 | new[] | test.cpp:548:5:548:19 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:543:14:543:27 | new[] | new[] | test.cpp:548:8:548:14 | src_pos | src_pos |
|
||||
| test.cpp:559:5:559:19 | ... = ... | test.cpp:554:14:554:27 | new[] | test.cpp:559:5:559:19 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:554:14:554:27 | new[] | new[] | test.cpp:559:8:559:14 | src_pos | src_pos |
|
||||
| test.cpp:647:5:647:19 | ... = ... | test.cpp:642:14:642:31 | new[] | test.cpp:647:5:647:19 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:642:14:642:31 | new[] | new[] | test.cpp:647:8:647:14 | src_pos | src_pos |
|
||||
| test.cpp:662:3:662:11 | ... = ... | test.cpp:652:14:652:27 | new[] | test.cpp:662:3:662:11 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@ + 1. | test.cpp:652:14:652:27 | new[] | new[] | test.cpp:653:19:653:22 | size | size |
|
||||
| test.cpp:675:7:675:23 | ... = ... | test.cpp:667:14:667:31 | new[] | test.cpp:675:7:675:23 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:667:14:667:31 | new[] | new[] | test.cpp:675:10:675:18 | ... ++ | ... ++ |
|
||||
| test.cpp:701:15:701:16 | * ... | test.cpp:695:13:695:26 | new[] | test.cpp:701:15:701:16 | * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:695:13:695:26 | new[] | new[] | test.cpp:696:19:696:22 | size | size |
|
||||
| test.cpp:706:12:706:13 | * ... | test.cpp:711:13:711:26 | new[] | test.cpp:706:12:706:13 | * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:711:13:711:26 | new[] | new[] | test.cpp:712:19:712:22 | size | size |
|
||||
| test.cpp:733:5:733:12 | ... = ... | test.cpp:730:12:730:28 | new[] | test.cpp:733:5:733:12 | ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:730:12:730:28 | new[] | new[] | test.cpp:732:21:732:25 | ... + ... | ... + ... |
|
||||
|
||||
@@ -305,7 +305,7 @@ void test21() {
|
||||
|
||||
for (int i = 0; i < n; i += 2) {
|
||||
xs[i] = test21_get(i); // GOOD
|
||||
xs[i+1] = test21_get(i+1); // $ alloc=L304 alloc=L304-1 deref=L308 // GOOD [FALSE POSITIVE]
|
||||
xs[i+1] = test21_get(i+1); // GOOD
|
||||
}
|
||||
}
|
||||
|
||||
@@ -659,7 +659,7 @@ void test32(unsigned size) {
|
||||
xs++;
|
||||
if (xs >= end)
|
||||
return;
|
||||
xs[0] = 0; // $ deref=L656->L662+1 deref=L657->L662+1 GOOD [FALSE POSITIVE]
|
||||
xs[0] = 0; // GOOD
|
||||
}
|
||||
|
||||
void test33(unsigned size, unsigned src_pos)
|
||||
@@ -672,7 +672,7 @@ void test33(unsigned size, unsigned src_pos)
|
||||
while (dst_pos < size - 1) {
|
||||
dst_pos++;
|
||||
if (true)
|
||||
xs[dst_pos++] = 0; // $ alloc=L667+1 deref=L675 // GOOD [FALSE POSITIVE]
|
||||
xs[dst_pos++] = 0; // GOOD
|
||||
}
|
||||
}
|
||||
|
||||
@@ -714,3 +714,22 @@ void test35(unsigned long size, char* q)
|
||||
deref(q);
|
||||
}
|
||||
}
|
||||
|
||||
void test21_simple(bool b) {
|
||||
int n = 0;
|
||||
if (b) n = 2;
|
||||
|
||||
int* xs = new int[n];
|
||||
|
||||
for (int i = 0; i < n; i += 2) {
|
||||
xs[i+1] = 0; // GOOD
|
||||
}
|
||||
}
|
||||
|
||||
void test36(unsigned size, unsigned n) {
|
||||
int* p = new int[size + 2];
|
||||
if(n < size + 1) {
|
||||
int* end = p + (n + 2); // $ alloc=L730+2
|
||||
*end = 0; // $ deref=L733 // BAD
|
||||
}
|
||||
}
|
||||
|
||||
@@ -732,7 +732,7 @@ void test_does_not_write_source_to_dereference()
|
||||
{
|
||||
int x;
|
||||
does_not_write_source_to_dereference(&x);
|
||||
sink(x); // $ ast,ir=733:7 SPURIOUS: ast,ir=726:11
|
||||
sink(x); // $ ast=733:7 ir SPURIOUS: ast=726:11
|
||||
}
|
||||
|
||||
void sometimes_calls_sink_eq(int x, int n) {
|
||||
|
||||
@@ -134,7 +134,7 @@ void pointer_test() {
|
||||
sink(*p3); // $ ast,ir
|
||||
|
||||
*p3 = 0;
|
||||
sink(*p3); // $ SPURIOUS: ast,ir
|
||||
sink(*p3); // $ SPURIOUS: ast
|
||||
}
|
||||
|
||||
// --- return values ---
|
||||
|
||||
@@ -195,18 +195,18 @@ int test13(char c, int i) {
|
||||
int z = i+1; // $ overflow=+
|
||||
range(z); // $ range="==InitializeParameter: i+1"
|
||||
range(c + i + uc + x + y + z); // $ overflow=+- overflow=+ overflow=- MISSING: range=>=1
|
||||
range((double)(c + i + uc + x + y + z)); // $ overflow=+ overflow=+- overflow=- range=<=4294967295 MISSING: range=>=1
|
||||
range((double)(c + i + uc + x + y + z)); // $ overflow=+ overflow=+- overflow=- MISSING: range=>=1
|
||||
return (double)(c + i + uc + x + y + z); // $ overflow=+- overflow=+ overflow=-
|
||||
}
|
||||
|
||||
// Regression test for ODASA-6013.
|
||||
int test14(int x) {
|
||||
int x0 = (int)(char)x;
|
||||
range(x0); // $ range=<=127 range=>=-128
|
||||
range(x0);
|
||||
int x1 = (int)(unsigned char)x;
|
||||
range(x1); // $ range=<=255 range=>=0
|
||||
range(x1);
|
||||
int x2 = (int)(unsigned short)x;
|
||||
range(x2); // $ range=<=65535 range=>=0
|
||||
range(x2);
|
||||
int x3 = (int)(unsigned int)x;
|
||||
range(x3);
|
||||
char c0 = x;
|
||||
@@ -759,9 +759,9 @@ unsigned long mult_overflow() {
|
||||
unsigned long mult_lower_bound(unsigned int ui, unsigned long ul) {
|
||||
if (ui >= 10) {
|
||||
range(ui); // $ range=>=10
|
||||
range((unsigned long)ui); // $ range=>=10 range=<=4294967295
|
||||
unsigned long result = (unsigned long)ui * ui; // no overflow
|
||||
range(result); // $ range=>=100 range=<=18446744065119617024
|
||||
range((unsigned long)ui); // $ range=>=10
|
||||
unsigned long result = (unsigned long)ui * ui; // $ overflow=+
|
||||
range(result); // $ MISSING: range=>=100
|
||||
return result; // BUG: upper bound should be >= 18446744065119617025
|
||||
}
|
||||
if (ul >= 10) {
|
||||
@@ -888,7 +888,7 @@ void notequal_variations(short n, float f) {
|
||||
}
|
||||
|
||||
if (n >= 5) {
|
||||
if (2 * n - 10 == 0) { // no overflow
|
||||
if (2 * n - 10 == 0) { // $ overflow=+
|
||||
range(n); // $ range=>=5 MISSING: range===5
|
||||
return;
|
||||
}
|
||||
@@ -936,7 +936,7 @@ void two_bounds_from_one_test(short ss, unsigned short us) {
|
||||
range(ss); // -32768 .. 32767
|
||||
}
|
||||
|
||||
if (ss + 1 < sizeof(int)) { // $ overflow=-
|
||||
if (ss + 1 < sizeof(int)) { // $ overflow=+
|
||||
range(ss); // -1 .. 2
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
| tests2.cpp:17:3:17:8 | call to wcscpy | This 'call to wcscpy' operation requires 12 bytes but the destination is only 8 bytes. |
|
||||
| tests2.cpp:22:3:22:8 | call to wcscpy | This 'call to wcscpy' operation requires 16 bytes but the destination is only 12 bytes. |
|
||||
| tests2.cpp:27:3:27:8 | call to wcscpy | This 'call to wcscpy' operation requires 20 bytes but the destination is only 16 bytes. |
|
||||
| tests2.cpp:31:3:31:8 | call to wcscpy | This 'call to wcscpy' operation requires 24 bytes but the destination is only 20 bytes. |
|
||||
| tests2.cpp:36:3:36:8 | call to wcscpy | This 'call to wcscpy' operation requires 28 bytes but the destination is only 24 bytes. |
|
||||
| tests2.cpp:41:3:41:8 | call to wcscpy | This 'call to wcscpy' operation requires 32 bytes but the destination is only 28 bytes. |
|
||||
| tests2.cpp:46:3:46:8 | call to wcscpy | This 'call to wcscpy' operation requires 36 bytes but the destination is only 32 bytes. |
|
||||
| tests2.cpp:18:3:18:8 | call to wcscpy | This 'call to wcscpy' operation requires 12 bytes but the destination is only 8 bytes. |
|
||||
| tests2.cpp:23:3:23:8 | call to wcscpy | This 'call to wcscpy' operation requires 16 bytes but the destination is only 12 bytes. |
|
||||
| tests2.cpp:28:3:28:8 | call to wcscpy | This 'call to wcscpy' operation requires 20 bytes but the destination is only 16 bytes. |
|
||||
| tests2.cpp:32:3:32:8 | call to wcscpy | This 'call to wcscpy' operation requires 24 bytes but the destination is only 20 bytes. |
|
||||
| tests2.cpp:37:3:37:8 | call to wcscpy | This 'call to wcscpy' operation requires 28 bytes but the destination is only 24 bytes. |
|
||||
| tests2.cpp:42:3:42:8 | call to wcscpy | This 'call to wcscpy' operation requires 32 bytes but the destination is only 28 bytes. |
|
||||
| tests2.cpp:47:3:47:8 | call to wcscpy | This 'call to wcscpy' operation requires 36 bytes but the destination is only 32 bytes. |
|
||||
| tests.c:54:3:54:9 | call to sprintf | This 'call to sprintf' operation requires 11 bytes but the destination is only 10 bytes. |
|
||||
| tests.c:58:3:58:9 | call to sprintf | This 'call to sprintf' operation requires 11 bytes but the destination is only 10 bytes. |
|
||||
| tests.c:62:17:62:24 | buffer10 | This 'scanf string argument' operation requires 11 bytes but the destination is only 10 bytes. |
|
||||
|
||||
@@ -6,6 +6,7 @@ void *realloc(void *ptr, size_t size);
|
||||
void *calloc(size_t nmemb, size_t size);
|
||||
void free(void *ptr);
|
||||
wchar_t *wcscpy(wchar_t *s1, const wchar_t *s2);
|
||||
int snprintf(char *s, size_t n, const char *format, ...);
|
||||
|
||||
// --- Semmle tests ---
|
||||
|
||||
@@ -46,3 +47,18 @@ void tests2() {
|
||||
wcscpy(buffer, L"12345678"); // BAD: buffer overflow
|
||||
delete [] buffer;
|
||||
}
|
||||
|
||||
char* dest1 = "a";
|
||||
char* dest2 = "abcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
void test3() {
|
||||
const char src[] = "abcdefghijkl";
|
||||
dest1 = (char*)malloc(sizeof(src));
|
||||
if (!dest1)
|
||||
return;
|
||||
snprintf(dest1, sizeof(src), "%s", src); // GOOD
|
||||
dest2 = (char*)malloc(3);
|
||||
if (!dest2)
|
||||
return;
|
||||
snprintf(dest2, sizeof(src), "%s", src); // BAD [NOT DETECTED]: buffer overflow
|
||||
}
|
||||
|
||||
@@ -8,14 +8,13 @@ using System.Threading.Tasks;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Semmle.BuildAnalyser
|
||||
{
|
||||
/// <summary>
|
||||
/// Main implementation of the build analysis.
|
||||
/// </summary>
|
||||
internal sealed partial class BuildAnalysis : IDisposable
|
||||
internal sealed class BuildAnalysis : IDisposable
|
||||
{
|
||||
private readonly AssemblyCache assemblyCache;
|
||||
private readonly ProgressMonitor progressMonitor;
|
||||
@@ -29,6 +28,9 @@ namespace Semmle.BuildAnalyser
|
||||
private readonly Options options;
|
||||
private readonly DirectoryInfo sourceDir;
|
||||
private readonly DotNet dotnet;
|
||||
private readonly FileContent fileContent;
|
||||
private readonly TemporaryDirectory packageDirectory;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Performs a C# build analysis.
|
||||
@@ -55,6 +57,9 @@ namespace Semmle.BuildAnalyser
|
||||
|
||||
this.progressMonitor.FindingFiles(options.SrcDir);
|
||||
|
||||
packageDirectory = new TemporaryDirectory(ComputeTempDirectory(sourceDir.FullName));
|
||||
|
||||
this.fileContent = new FileContent(packageDirectory, progressMonitor, () => GetFiles("*.*"));
|
||||
this.allSources = GetFiles("*.cs").ToArray();
|
||||
var allProjects = GetFiles("*.csproj");
|
||||
var solutions = options.SolutionFile is not null
|
||||
@@ -63,12 +68,19 @@ namespace Semmle.BuildAnalyser
|
||||
|
||||
var dllDirNames = options.DllDirs.Select(Path.GetFullPath).ToList();
|
||||
|
||||
// Find DLLs in the .Net Framework
|
||||
// Find DLLs in the .Net / Asp.Net Framework
|
||||
if (options.ScanNetFrameworkDlls)
|
||||
{
|
||||
var runtimeLocation = new Runtime(dotnet).GetRuntime(options.UseSelfContainedDotnet);
|
||||
progressMonitor.Log(Util.Logging.Severity.Debug, $"Runtime location selected: {runtimeLocation}");
|
||||
var runtime = new Runtime(dotnet);
|
||||
var runtimeLocation = runtime.GetRuntime(options.UseSelfContainedDotnet);
|
||||
progressMonitor.LogInfo($".NET runtime location selected: {runtimeLocation}");
|
||||
dllDirNames.Add(runtimeLocation);
|
||||
|
||||
if (fileContent.UseAspNetDlls && runtime.GetAspRuntime() is string aspRuntime)
|
||||
{
|
||||
progressMonitor.LogInfo($"ASP.NET runtime location selected: {aspRuntime}");
|
||||
dllDirNames.Add(aspRuntime);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.UseMscorlib)
|
||||
@@ -76,8 +88,6 @@ namespace Semmle.BuildAnalyser
|
||||
UseReference(typeof(object).Assembly.Location);
|
||||
}
|
||||
|
||||
packageDirectory = new TemporaryDirectory(ComputeTempDirectory(sourceDir.FullName));
|
||||
|
||||
if (options.UseNuGet)
|
||||
{
|
||||
dllDirNames.Add(packageDirectory.DirInfo.FullName);
|
||||
@@ -187,6 +197,7 @@ namespace Semmle.BuildAnalyser
|
||||
{
|
||||
finalAssemblyList[r.Name] = r;
|
||||
}
|
||||
|
||||
// Update the used references list
|
||||
usedReferences.Clear();
|
||||
foreach (var r in finalAssemblyList.Select(r => r.Value.Filename))
|
||||
@@ -210,24 +221,18 @@ namespace Semmle.BuildAnalyser
|
||||
/// Store that a particular reference file is used.
|
||||
/// </summary>
|
||||
/// <param name="reference">The filename of the reference.</param>
|
||||
private void UseReference(string reference)
|
||||
{
|
||||
usedReferences[reference] = true;
|
||||
}
|
||||
private void UseReference(string reference) => usedReferences[reference] = true;
|
||||
|
||||
/// <summary>
|
||||
/// Store that a particular source file is used (by a project file).
|
||||
/// </summary>
|
||||
/// <param name="sourceFile">The source file.</param>
|
||||
private void UseSource(FileInfo sourceFile)
|
||||
{
|
||||
sources[sourceFile.FullName] = sourceFile.Exists;
|
||||
}
|
||||
private void UseSource(FileInfo sourceFile) => sources[sourceFile.FullName] = sourceFile.Exists;
|
||||
|
||||
/// <summary>
|
||||
/// The list of resolved reference files.
|
||||
/// </summary>
|
||||
public IEnumerable<string> ReferenceFiles => this.usedReferences.Keys;
|
||||
public IEnumerable<string> ReferenceFiles => usedReferences.Keys;
|
||||
|
||||
/// <summary>
|
||||
/// The list of source files used in projects.
|
||||
@@ -242,7 +247,7 @@ namespace Semmle.BuildAnalyser
|
||||
/// <summary>
|
||||
/// List of assembly IDs which couldn't be resolved.
|
||||
/// </summary>
|
||||
public IEnumerable<string> UnresolvedReferences => this.unresolvedReferences.Select(r => r.Key);
|
||||
public IEnumerable<string> UnresolvedReferences => unresolvedReferences.Select(r => r.Key);
|
||||
|
||||
/// <summary>
|
||||
/// List of source files which were mentioned in project files but
|
||||
@@ -256,12 +261,7 @@ namespace Semmle.BuildAnalyser
|
||||
/// </summary>
|
||||
/// <param name="id">The assembly ID.</param>
|
||||
/// <param name="projectFile">The project file making the reference.</param>
|
||||
private void UnresolvedReference(string id, string projectFile)
|
||||
{
|
||||
unresolvedReferences[id] = projectFile;
|
||||
}
|
||||
|
||||
private readonly TemporaryDirectory packageDirectory;
|
||||
private void UnresolvedReference(string id, string projectFile) => unresolvedReferences[id] = projectFile;
|
||||
|
||||
/// <summary>
|
||||
/// Reads all the source files and references from the given list of projects.
|
||||
@@ -318,10 +318,8 @@ namespace Semmle.BuildAnalyser
|
||||
|
||||
}
|
||||
|
||||
private bool Restore(string target, string? pathToNugetConfig = null)
|
||||
{
|
||||
return dotnet.RestoreToDirectory(target, packageDirectory.DirInfo.FullName, pathToNugetConfig);
|
||||
}
|
||||
private bool Restore(string target, string? pathToNugetConfig = null) =>
|
||||
dotnet.RestoreToDirectory(target, packageDirectory.DirInfo.FullName, pathToNugetConfig);
|
||||
|
||||
private void Restore(IEnumerable<string> targets, string? pathToNugetConfig = null)
|
||||
{
|
||||
@@ -331,11 +329,9 @@ namespace Semmle.BuildAnalyser
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void DownloadMissingPackages(IEnumerable<string> restoreTargets)
|
||||
{
|
||||
var alreadyDownloadedPackages = Directory.GetDirectories(packageDirectory.DirInfo.FullName).Select(d => Path.GetFileName(d).ToLowerInvariant()).ToHashSet();
|
||||
var notYetDownloadedPackages = new HashSet<string>();
|
||||
|
||||
var nugetConfigs = GetFiles("nuget.config", recurseSubdirectories: true).ToArray();
|
||||
string? nugetConfig = null;
|
||||
if (nugetConfigs.Length > 1)
|
||||
@@ -352,46 +348,7 @@ namespace Semmle.BuildAnalyser
|
||||
nugetConfig = nugetConfigs.FirstOrDefault();
|
||||
}
|
||||
|
||||
var allFiles = GetFiles("*.*");
|
||||
foreach (var file in allFiles)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var sr = new StreamReader(file);
|
||||
ReadOnlySpan<char> line;
|
||||
while ((line = sr.ReadLine()) != null)
|
||||
{
|
||||
foreach (var valueMatch in PackageReference().EnumerateMatches(line))
|
||||
{
|
||||
// We can't get the group from the ValueMatch, so doing it manually:
|
||||
var match = line.Slice(valueMatch.Index, valueMatch.Length);
|
||||
var includeIndex = match.IndexOf("Include", StringComparison.InvariantCultureIgnoreCase);
|
||||
if (includeIndex == -1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
match = match.Slice(includeIndex + "Include".Length + 1);
|
||||
|
||||
var quoteIndex1 = match.IndexOf("\"");
|
||||
var quoteIndex2 = match.Slice(quoteIndex1 + 1).IndexOf("\"");
|
||||
|
||||
var packageName = match.Slice(quoteIndex1 + 1, quoteIndex2).ToString().ToLowerInvariant();
|
||||
if (!alreadyDownloadedPackages.Contains(packageName))
|
||||
{
|
||||
notYetDownloadedPackages.Add(packageName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
progressMonitor.FailedToReadFile(file, ex);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var package in notYetDownloadedPackages)
|
||||
foreach (var package in fileContent.NotYetDownloadedPackages)
|
||||
{
|
||||
progressMonitor.NugetInstall(package);
|
||||
using var tempDir = new TemporaryDirectory(ComputeTempDirectory(package));
|
||||
@@ -434,12 +391,6 @@ namespace Semmle.BuildAnalyser
|
||||
});
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
packageDirectory?.Dispose();
|
||||
}
|
||||
|
||||
[GeneratedRegex("<PackageReference .*Include=\"(.*?)\".*/>", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline)]
|
||||
private static partial Regex PackageReference();
|
||||
public void Dispose() => packageDirectory?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Semmle.BuildAnalyser
|
||||
bool RestoreToDirectory(string project, string directory, string? pathToNugetConfig = null);
|
||||
bool New(string folder);
|
||||
bool AddPackage(string folder, string package);
|
||||
public IList<string> GetListedRuntimes();
|
||||
IList<string> GetListedRuntimes();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -78,7 +78,8 @@ namespace Semmle.BuildAnalyser
|
||||
|
||||
public IList<string> GetListedRuntimes()
|
||||
{
|
||||
var args = "--list-runtimes";
|
||||
const string args = "--list-runtimes";
|
||||
progressMonitor.RunningProcess($"{dotnet} {args}");
|
||||
var pi = new ProcessStartInfo(dotnet, args)
|
||||
{
|
||||
RedirectStandardOutput = true,
|
||||
@@ -90,6 +91,7 @@ namespace Semmle.BuildAnalyser
|
||||
progressMonitor.CommandFailed(dotnet, args, exitCode);
|
||||
return new List<string>();
|
||||
}
|
||||
progressMonitor.LogInfo($"Found runtimes: {string.Join("\n", runtimes)}");
|
||||
return runtimes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
using Semmle.Util;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Semmle.BuildAnalyser
|
||||
{
|
||||
|
||||
// <summary>
|
||||
// This class is used to read a set of files and decide different properties about the
|
||||
// content (by reading the content of the files only once).
|
||||
// The implementation is lazy, so the properties are only calculated when
|
||||
// the first property is accessed.
|
||||
// </summary>
|
||||
internal partial class FileContent
|
||||
{
|
||||
private readonly ProgressMonitor progressMonitor;
|
||||
private readonly IUnsafeFileReader unsafeFileReader;
|
||||
private readonly Func<IEnumerable<string>> getFiles;
|
||||
private readonly Func<HashSet<string>> getAlreadyDownloadedPackages;
|
||||
private readonly HashSet<string> notYetDownloadedPackages = new HashSet<string>();
|
||||
private readonly Initializer initialize;
|
||||
|
||||
public HashSet<string> NotYetDownloadedPackages
|
||||
{
|
||||
get
|
||||
{
|
||||
initialize.Run();
|
||||
return notYetDownloadedPackages;
|
||||
}
|
||||
}
|
||||
|
||||
private bool useAspNetDlls = false;
|
||||
|
||||
/// <summary>
|
||||
/// True if any file in the source directory indicates that ASP.NET is used.
|
||||
/// The following heuristic is used to decide, if ASP.NET is used:
|
||||
/// If any file in the source directory contains something like (this will most like be a .csproj file)
|
||||
/// <Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
/// <FrameworkReference Include="Microsoft.AspNetCore.App"/>
|
||||
/// </summary>
|
||||
public bool UseAspNetDlls
|
||||
{
|
||||
get
|
||||
{
|
||||
initialize.Run();
|
||||
return useAspNetDlls;
|
||||
}
|
||||
}
|
||||
|
||||
internal FileContent(Func<HashSet<string>> getAlreadyDownloadedPackages,
|
||||
ProgressMonitor progressMonitor,
|
||||
Func<IEnumerable<string>> getFiles,
|
||||
IUnsafeFileReader unsafeFileReader)
|
||||
{
|
||||
this.getAlreadyDownloadedPackages = getAlreadyDownloadedPackages;
|
||||
this.progressMonitor = progressMonitor;
|
||||
this.getFiles = getFiles;
|
||||
this.unsafeFileReader = unsafeFileReader;
|
||||
this.initialize = new Initializer(DoInitialize);
|
||||
}
|
||||
|
||||
|
||||
public FileContent(TemporaryDirectory packageDirectory, ProgressMonitor progressMonitor, Func<IEnumerable<string>> getFiles) : this(() => Directory.GetDirectories(packageDirectory.DirInfo.FullName)
|
||||
.Select(d => Path.GetFileName(d)
|
||||
.ToLowerInvariant())
|
||||
.ToHashSet(), progressMonitor, getFiles, new UnsafeFileReader())
|
||||
{ }
|
||||
|
||||
private static string GetGroup(ReadOnlySpan<char> input, ValueMatch valueMatch, string groupPrefix)
|
||||
{
|
||||
var match = input.Slice(valueMatch.Index, valueMatch.Length);
|
||||
var includeIndex = match.IndexOf(groupPrefix, StringComparison.InvariantCultureIgnoreCase);
|
||||
if (includeIndex == -1)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
match = match.Slice(includeIndex + groupPrefix.Length + 1);
|
||||
|
||||
var quoteIndex1 = match.IndexOf("\"");
|
||||
var quoteIndex2 = match.Slice(quoteIndex1 + 1).IndexOf("\"");
|
||||
|
||||
return match.Slice(quoteIndex1 + 1, quoteIndex2).ToString().ToLowerInvariant();
|
||||
}
|
||||
|
||||
private static bool IsGroupMatch(ReadOnlySpan<char> line, Regex regex, string groupPrefix, string value)
|
||||
{
|
||||
foreach (var valueMatch in regex.EnumerateMatches(line))
|
||||
{
|
||||
// We can't get the group from the ValueMatch, so doing it manually:
|
||||
if (GetGroup(line, valueMatch, groupPrefix) == value.ToLowerInvariant())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void DoInitialize()
|
||||
{
|
||||
var alreadyDownloadedPackages = getAlreadyDownloadedPackages();
|
||||
foreach (var file in getFiles())
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (ReadOnlySpan<char> line in unsafeFileReader.ReadLines(file))
|
||||
{
|
||||
|
||||
// Find the not yet downloaded packages.
|
||||
foreach (var valueMatch in PackageReference().EnumerateMatches(line))
|
||||
{
|
||||
// We can't get the group from the ValueMatch, so doing it manually:
|
||||
var packageName = GetGroup(line, valueMatch, "Include");
|
||||
if (!string.IsNullOrEmpty(packageName) && !alreadyDownloadedPackages.Contains(packageName))
|
||||
{
|
||||
notYetDownloadedPackages.Add(packageName);
|
||||
}
|
||||
}
|
||||
|
||||
// Determine if ASP.NET is used.
|
||||
if (!useAspNetDlls)
|
||||
{
|
||||
useAspNetDlls =
|
||||
IsGroupMatch(line, ProjectSdk(), "Sdk", "Microsoft.NET.Sdk.Web") ||
|
||||
IsGroupMatch(line, FrameworkReference(), "Include", "Microsoft.AspNetCore.App");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
progressMonitor.FailedToReadFile(file, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[GeneratedRegex("<PackageReference.*\\sInclude=\"(.*?)\".*/?>", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline)]
|
||||
private static partial Regex PackageReference();
|
||||
|
||||
[GeneratedRegex("<FrameworkReference.*\\sInclude=\"(.*?)\".*/?>", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline)]
|
||||
private static partial Regex FrameworkReference();
|
||||
|
||||
[GeneratedRegex("<(.*\\s)?Project.*\\sSdk=\"(.*?)\".*/?>", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline)]
|
||||
private static partial Regex ProjectSdk();
|
||||
}
|
||||
}
|
||||
|
||||
internal interface IUnsafeFileReader
|
||||
{
|
||||
IEnumerable<string> ReadLines(string file);
|
||||
}
|
||||
|
||||
internal class UnsafeFileReader : IUnsafeFileReader
|
||||
{
|
||||
public IEnumerable<string> ReadLines(string file)
|
||||
{
|
||||
using var sr = new StreamReader(file);
|
||||
string? line;
|
||||
while ((line = sr.ReadLine()) != null)
|
||||
{
|
||||
yield return line;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,121 +12,101 @@ namespace Semmle.BuildAnalyser
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public void FindingFiles(string dir)
|
||||
{
|
||||
logger.Log(Severity.Info, "Finding files in {0}...", dir);
|
||||
}
|
||||
public void Log(Severity severity, string message) =>
|
||||
logger.Log(severity, message);
|
||||
|
||||
public void LogInfo(string message) =>
|
||||
logger.Log(Severity.Info, message);
|
||||
|
||||
private void LogDebug(string message) =>
|
||||
logger.Log(Severity.Debug, message);
|
||||
|
||||
private void LogError(string message) =>
|
||||
logger.Log(Severity.Error, message);
|
||||
|
||||
public void FindingFiles(string dir) =>
|
||||
LogInfo($"Finding files in {dir}...");
|
||||
|
||||
public void IndexingReferences(int count)
|
||||
{
|
||||
logger.Log(Severity.Info, "Indexing...");
|
||||
logger.Log(Severity.Debug, "Indexing {0} DLLs...", count);
|
||||
LogInfo("Indexing...");
|
||||
LogDebug($"Indexing {count} DLLs...");
|
||||
}
|
||||
|
||||
public void UnresolvedReference(string id, string project)
|
||||
{
|
||||
logger.Log(Severity.Info, "Unresolved reference {0}", id);
|
||||
logger.Log(Severity.Debug, "Unresolved {0} referenced by {1}", id, project);
|
||||
LogInfo($"Unresolved reference {id}");
|
||||
LogDebug($"Unresolved {id} referenced by {project}");
|
||||
}
|
||||
|
||||
public void AnalysingSolution(string filename)
|
||||
{
|
||||
logger.Log(Severity.Info, $"Analyzing {filename}...");
|
||||
}
|
||||
public void AnalysingSolution(string filename) =>
|
||||
LogInfo($"Analyzing {filename}...");
|
||||
|
||||
public void FailedProjectFile(string filename, string reason)
|
||||
{
|
||||
logger.Log(Severity.Info, "Couldn't read project file {0}: {1}", filename, reason);
|
||||
}
|
||||
public void FailedProjectFile(string filename, string reason) =>
|
||||
LogInfo($"Couldn't read project file {filename}: {reason}");
|
||||
|
||||
public void FailedNugetCommand(string exe, string args, string message)
|
||||
{
|
||||
logger.Log(Severity.Info, "Command failed: {0} {1}", exe, args);
|
||||
logger.Log(Severity.Info, " {0}", message);
|
||||
LogInfo($"Command failed: {exe} {args}");
|
||||
LogInfo($" {message}");
|
||||
}
|
||||
|
||||
public void NugetInstall(string package)
|
||||
{
|
||||
logger.Log(Severity.Info, "Restoring {0}...", package);
|
||||
}
|
||||
public void NugetInstall(string package) =>
|
||||
LogInfo($"Restoring {package}...");
|
||||
|
||||
public void ResolvedReference(string filename)
|
||||
{
|
||||
logger.Log(Severity.Info, "Resolved {0}", filename);
|
||||
}
|
||||
public void ResolvedReference(string filename) =>
|
||||
LogInfo($"Resolved {filename}");
|
||||
|
||||
public void Summary(int existingSources, int usedSources, int missingSources,
|
||||
int references, int unresolvedReferences,
|
||||
int resolvedConflicts, int totalProjects, int failedProjects,
|
||||
TimeSpan analysisTime)
|
||||
{
|
||||
logger.Log(Severity.Info, "");
|
||||
logger.Log(Severity.Info, "Build analysis summary:");
|
||||
logger.Log(Severity.Info, "{0, 6} source files in the filesystem", existingSources);
|
||||
logger.Log(Severity.Info, "{0, 6} source files in project files", usedSources);
|
||||
logger.Log(Severity.Info, "{0, 6} sources missing from project files", missingSources);
|
||||
logger.Log(Severity.Info, "{0, 6} resolved references", references);
|
||||
logger.Log(Severity.Info, "{0, 6} unresolved references", unresolvedReferences);
|
||||
logger.Log(Severity.Info, "{0, 6} resolved assembly conflicts", resolvedConflicts);
|
||||
logger.Log(Severity.Info, "{0, 6} projects", totalProjects);
|
||||
logger.Log(Severity.Info, "{0, 6} missing/failed projects", failedProjects);
|
||||
logger.Log(Severity.Info, "Build analysis completed in {0}", analysisTime);
|
||||
const int align = 6;
|
||||
LogInfo("");
|
||||
LogInfo("Build analysis summary:");
|
||||
LogInfo($"{existingSources,align} source files in the filesystem");
|
||||
LogInfo($"{usedSources,align} source files in project files");
|
||||
LogInfo($"{missingSources,align} sources missing from project files");
|
||||
LogInfo($"{references,align} resolved references");
|
||||
LogInfo($"{unresolvedReferences,align} unresolved references");
|
||||
LogInfo($"{resolvedConflicts,align} resolved assembly conflicts");
|
||||
LogInfo($"{totalProjects,align} projects");
|
||||
LogInfo($"{failedProjects,align} missing/failed projects");
|
||||
LogInfo($"Build analysis completed in {analysisTime}");
|
||||
}
|
||||
|
||||
public void Log(Severity severity, string message)
|
||||
{
|
||||
logger.Log(severity, message);
|
||||
}
|
||||
public void ResolvedConflict(string asm1, string asm2) =>
|
||||
LogDebug($"Resolved {asm1} as {asm2}");
|
||||
|
||||
public void ResolvedConflict(string asm1, string asm2)
|
||||
{
|
||||
logger.Log(Severity.Debug, "Resolved {0} as {1}", asm1, asm2);
|
||||
}
|
||||
public void MissingProject(string projectFile) =>
|
||||
LogInfo($"Solution is missing {projectFile}");
|
||||
|
||||
public void MissingProject(string projectFile)
|
||||
{
|
||||
logger.Log(Severity.Info, "Solution is missing {0}", projectFile);
|
||||
}
|
||||
public void CommandFailed(string exe, string arguments, int exitCode) =>
|
||||
LogError($"Command {exe} {arguments} failed with exit code {exitCode}");
|
||||
|
||||
public void CommandFailed(string exe, string arguments, int exitCode)
|
||||
{
|
||||
logger.Log(Severity.Error, $"Command {exe} {arguments} failed with exit code {exitCode}");
|
||||
}
|
||||
public void MissingNuGet() =>
|
||||
LogError("Missing nuget.exe");
|
||||
|
||||
public void MissingNuGet()
|
||||
{
|
||||
logger.Log(Severity.Error, "Missing nuget.exe");
|
||||
}
|
||||
public void MissingDotNet() =>
|
||||
LogError("Missing dotnet CLI");
|
||||
|
||||
public void MissingDotNet()
|
||||
{
|
||||
logger.Log(Severity.Error, "Missing dotnet CLI");
|
||||
}
|
||||
public void RunningProcess(string command) =>
|
||||
LogInfo($"Running {command}");
|
||||
|
||||
public void RunningProcess(string command)
|
||||
{
|
||||
logger.Log(Severity.Info, $"Running {command}");
|
||||
}
|
||||
|
||||
public void FailedToRestoreNugetPackage(string package)
|
||||
{
|
||||
logger.Log(Severity.Info, $"Failed to restore nuget package {package}");
|
||||
}
|
||||
public void FailedToRestoreNugetPackage(string package) =>
|
||||
LogInfo($"Failed to restore nuget package {package}");
|
||||
|
||||
public void FailedToReadFile(string file, Exception ex)
|
||||
{
|
||||
logger.Log(Severity.Info, $"Failed to read file {file}");
|
||||
logger.Log(Severity.Debug, $"Failed to read file {file}, exception: {ex}");
|
||||
LogInfo($"Failed to read file {file}");
|
||||
LogDebug($"Failed to read file {file}, exception: {ex}");
|
||||
}
|
||||
|
||||
public void MultipleNugetConfig(string[] nugetConfigs)
|
||||
{
|
||||
logger.Log(Severity.Info, $"Found multiple nuget.config files: {string.Join(", ", nugetConfigs)}.");
|
||||
}
|
||||
public void MultipleNugetConfig(string[] nugetConfigs) =>
|
||||
LogInfo($"Found multiple nuget.config files: {string.Join(", ", nugetConfigs)}.");
|
||||
|
||||
internal void NoTopLevelNugetConfig()
|
||||
{
|
||||
logger.Log(Severity.Info, $"Could not find a top-level nuget.config file.");
|
||||
}
|
||||
internal void NoTopLevelNugetConfig() =>
|
||||
LogInfo("Could not find a top-level nuget.config file.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,9 +18,15 @@ namespace Semmle.Extraction.CSharp.Standalone
|
||||
private const string aspNetCoreApp = "Microsoft.AspNetCore.App";
|
||||
|
||||
private readonly IDotNet dotNet;
|
||||
private readonly Lazy<Dictionary<string, RuntimeVersion>> newestRuntimes;
|
||||
private Dictionary<string, RuntimeVersion> NewestRuntimes => newestRuntimes.Value;
|
||||
private static string ExecutingRuntime => RuntimeEnvironment.GetRuntimeDirectory();
|
||||
|
||||
public Runtime(IDotNet dotNet) => this.dotNet = dotNet;
|
||||
public Runtime(IDotNet dotNet)
|
||||
{
|
||||
this.dotNet = dotNet;
|
||||
this.newestRuntimes = new(GetNewestRuntimes);
|
||||
}
|
||||
|
||||
internal record RuntimeVersion : IComparable<RuntimeVersion>
|
||||
{
|
||||
@@ -74,7 +80,7 @@ namespace Semmle.Extraction.CSharp.Standalone
|
||||
public override string ToString() => FullPath;
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"^(\S+)\s(\d+\.\d+\.\d+)(-([a-z]+)\.(\d+\.\d+\.\d+))?\s\[(\S+)\]$")]
|
||||
[GeneratedRegex(@"^(\S+)\s(\d+\.\d+\.\d+)(-([a-z]+)\.(\d+\.\d+\.\d+))?\s\[(.+)\]$")]
|
||||
private static partial Regex RuntimeRegex();
|
||||
|
||||
/// <summary>
|
||||
@@ -140,33 +146,42 @@ namespace Semmle.Extraction.CSharp.Standalone
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetRuntimes()
|
||||
/// <summary>
|
||||
/// Gets the .NET runtime location to use for extraction.
|
||||
/// </summary>
|
||||
public string GetRuntime(bool useSelfContained)
|
||||
{
|
||||
// Gets the newest version of the installed runtimes.
|
||||
var newestRuntimes = GetNewestRuntimes();
|
||||
if (useSelfContained)
|
||||
{
|
||||
return ExecutingRuntime;
|
||||
}
|
||||
|
||||
// Location of the newest .NET Core Runtime.
|
||||
if (newestRuntimes.TryGetValue(netCoreApp, out var netCoreVersion))
|
||||
if (NewestRuntimes.TryGetValue(netCoreApp, out var netCoreVersion))
|
||||
{
|
||||
yield return netCoreVersion.FullPath;
|
||||
return netCoreVersion.FullPath;
|
||||
}
|
||||
|
||||
// Location of the newest ASP.NET Core Runtime.
|
||||
if (newestRuntimes.TryGetValue(aspNetCoreApp, out var aspNetCoreVersion))
|
||||
if (DesktopRuntimes.Any())
|
||||
{
|
||||
yield return aspNetCoreVersion.FullPath;
|
||||
return DesktopRuntimes.First();
|
||||
}
|
||||
|
||||
foreach (var r in DesktopRuntimes)
|
||||
yield return r;
|
||||
|
||||
// A bad choice if it's the self-contained runtime distributed in codeql dist.
|
||||
yield return ExecutingRuntime;
|
||||
return ExecutingRuntime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the .NET runtime location to use for extraction
|
||||
/// Gets the ASP.NET runtime location to use for extraction, if one exists.
|
||||
/// </summary>
|
||||
public string GetRuntime(bool useSelfContained) => useSelfContained ? ExecutingRuntime : GetRuntimes().First();
|
||||
public string? GetAspRuntime()
|
||||
{
|
||||
// Location of the newest ASP.NET Core Runtime.
|
||||
if (NewestRuntimes.TryGetValue(aspNetCoreApp, out var aspNetCoreVersion))
|
||||
{
|
||||
return aspNetCoreVersion.FullPath;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
95
csharp/extractor/Semmle.Extraction.Tests/FileContent.cs
Normal file
95
csharp/extractor/Semmle.Extraction.Tests/FileContent.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using Xunit;
|
||||
using Semmle.BuildAnalyser;
|
||||
using Semmle.Util.Logging;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Semmle.Extraction.Tests
|
||||
{
|
||||
|
||||
internal class LoggerStub : ILogger
|
||||
{
|
||||
public void Log(Severity severity, string message) { }
|
||||
|
||||
public void Dispose() { }
|
||||
}
|
||||
|
||||
internal class UnsafeFileReaderStub : IUnsafeFileReader
|
||||
{
|
||||
private readonly List<string> lines;
|
||||
|
||||
public UnsafeFileReaderStub(List<string> lines)
|
||||
{
|
||||
this.lines = lines;
|
||||
}
|
||||
|
||||
public IEnumerable<string> ReadLines(string file)
|
||||
{
|
||||
foreach (var line in lines)
|
||||
{
|
||||
yield return line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class TestFileContent : FileContent
|
||||
{
|
||||
public TestFileContent(List<string> lines) : base(() => new HashSet<string>(),
|
||||
new ProgressMonitor(new LoggerStub()),
|
||||
() => new List<string>() { "test1.cs" },
|
||||
new UnsafeFileReaderStub(lines))
|
||||
{ }
|
||||
}
|
||||
|
||||
public class FileContentTests
|
||||
{
|
||||
[Fact]
|
||||
public void TestFileContent1()
|
||||
{
|
||||
// Setup
|
||||
var lines = new List<string>()
|
||||
{
|
||||
"<Project Sdk=\"Microsoft.NET.Sdk\">",
|
||||
"<PackageReference Include=\"DotNetAnalyzers.DocumentationAnalyzers\" Version=\"1.0.0-beta.59\" PrivateAssets=\"all\" />",
|
||||
"<PackageReference Version=\"7.0.0\" Include=\"Microsoft.CodeAnalysis.NetAnalyzers\"PrivateAssets=\"all\" />",
|
||||
"<PackageReference Include=\"StyleCop.Analyzers\" Version=\"1.2.0-beta.406\">",
|
||||
"<FrameworkReference Include=\"My.Framework\"/>"
|
||||
};
|
||||
var fileContent = new TestFileContent(lines);
|
||||
|
||||
// Execute
|
||||
var notYetDownloadedPackages = fileContent.NotYetDownloadedPackages;
|
||||
var useAspNetDlls = fileContent.UseAspNetDlls;
|
||||
|
||||
// Verify
|
||||
Assert.False(useAspNetDlls);
|
||||
Assert.Equal(3, notYetDownloadedPackages.Count);
|
||||
Assert.Contains("DotNetAnalyzers.DocumentationAnalyzers".ToLowerInvariant(), notYetDownloadedPackages);
|
||||
Assert.Contains("Microsoft.CodeAnalysis.NetAnalyzers".ToLowerInvariant(), notYetDownloadedPackages);
|
||||
Assert.Contains("StyleCop.Analyzers".ToLowerInvariant(), notYetDownloadedPackages);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestFileContent2()
|
||||
{
|
||||
// Setup
|
||||
var lines = new List<string>()
|
||||
{
|
||||
"<Project Sdk=\"Microsoft.NET.Sdk.Web\">",
|
||||
"<FrameworkReference Include=\"My.Framework\"/>",
|
||||
"<PackageReference Version=\"7.0.0\" Include=\"Microsoft.CodeAnalysis.NetAnalyzers\"PrivateAssets=\"all\" />",
|
||||
"<PackageReference Include=\"StyleCop.Analyzers\" Version=\"1.2.0-beta.406\">"
|
||||
};
|
||||
var fileContent = new TestFileContent(lines);
|
||||
|
||||
// Execute
|
||||
var useAspNetDlls = fileContent.UseAspNetDlls;
|
||||
var notYetDownloadedPackages = fileContent.NotYetDownloadedPackages;
|
||||
|
||||
// Verify
|
||||
Assert.True(useAspNetDlls);
|
||||
Assert.Equal(2, notYetDownloadedPackages.Count);
|
||||
Assert.Contains("Microsoft.CodeAnalysis.NetAnalyzers".ToLowerInvariant(), notYetDownloadedPackages);
|
||||
Assert.Contains("StyleCop.Analyzers".ToLowerInvariant(), notYetDownloadedPackages);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -100,5 +100,36 @@ namespace Semmle.Extraction.Tests
|
||||
Assert.Equal("/path/dotnet/shared/Microsoft.NETCore.App/8.0.0-rc.4.43280.8", FixExpectedPathOnWindows(netCoreApp.FullPath));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestRuntime4()
|
||||
{
|
||||
// Setup
|
||||
var listedRuntimes = new List<string>
|
||||
{
|
||||
@"Microsoft.AspNetCore.App 6.0.5 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]",
|
||||
@"Microsoft.AspNetCore.App 6.0.20 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]",
|
||||
@"Microsoft.AspNetCore.App 7.0.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]",
|
||||
@"Microsoft.NETCore.App 6.0.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]",
|
||||
@"Microsoft.NETCore.App 6.0.20 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]",
|
||||
@"Microsoft.NETCore.App 7.0.2 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]",
|
||||
@"Microsoft.WindowsDesktop.App 6.0.5 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]",
|
||||
@"Microsoft.WindowsDesktop.App 6.0.20 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]",
|
||||
@"Microsoft.WindowsDesktop.App 7.0.4 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]"
|
||||
};
|
||||
var dotnet = new DotNetStub(listedRuntimes);
|
||||
var runtime = new Runtime(dotnet);
|
||||
|
||||
// Execute
|
||||
var runtimes = runtime.GetNewestRuntimes();
|
||||
|
||||
// Verify
|
||||
Assert.Equal(3, runtimes.Count);
|
||||
|
||||
Assert.True(runtimes.TryGetValue("Microsoft.AspNetCore.App", out var aspNetCoreApp));
|
||||
Assert.Equal(@"C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/7.0.2", FixExpectedPathOnWindows(aspNetCoreApp.FullPath));
|
||||
|
||||
Assert.True(runtimes.TryGetValue("Microsoft.NETCore.App", out var netCoreApp));
|
||||
Assert.Equal(@"C:/Program Files/dotnet/shared/Microsoft.NETCore.App/7.0.2", FixExpectedPathOnWindows(netCoreApp.FullPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
28
csharp/extractor/Semmle.Util/Initializer.cs
Normal file
28
csharp/extractor/Semmle.Util/Initializer.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
|
||||
namespace Semmle.Util
|
||||
{
|
||||
/// <summary>
|
||||
/// An instance of this class is used to ensure that the provided
|
||||
/// action is executed only once and on the first call to `Run`.
|
||||
/// It is thread-safe.
|
||||
/// </summary>
|
||||
public class Initializer
|
||||
{
|
||||
private readonly Lazy<bool> doInit;
|
||||
|
||||
public Initializer(Action action)
|
||||
{
|
||||
doInit = new Lazy<bool>(() =>
|
||||
{
|
||||
action();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public void Run()
|
||||
{
|
||||
var _ = doInit.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,7 @@
|
||||
## 1.6.2
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 1.6.1
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
## 1.6.2
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 1.6.1
|
||||
lastReleaseVersion: 1.6.2
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/csharp-solorigate-all
|
||||
version: 1.6.2-dev
|
||||
version: 1.6.3-dev
|
||||
groups:
|
||||
- csharp
|
||||
- solorigate
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
## 1.6.2
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 1.6.1
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
## 1.6.2
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 1.6.1
|
||||
lastReleaseVersion: 1.6.2
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/csharp-solorigate-queries
|
||||
version: 1.6.2-dev
|
||||
version: 1.6.3-dev
|
||||
groups:
|
||||
- csharp
|
||||
- solorigate
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
## 0.7.2
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.7.1
|
||||
|
||||
### New Features
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
* Provides helper classes and methods related to LINQ.
|
||||
*/
|
||||
|
||||
import csharp
|
||||
private import csharp
|
||||
private import semmle.code.csharp.frameworks.system.collections.Generic as GenericCollections
|
||||
private import semmle.code.csharp.frameworks.system.Collections as Collections
|
||||
|
||||
//#################### PREDICATES ####################
|
||||
private Stmt firstStmt(ForeachStmt fes) {
|
||||
@@ -29,13 +31,40 @@ predicate isIEnumerableType(ValueOrRefType t) {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A class of foreach statements where the iterable expression
|
||||
* supports the use of the LINQ extension methods on `IEnumerable<T>`.
|
||||
*/
|
||||
class ForeachStmtGenericEnumerable extends ForeachStmt {
|
||||
ForeachStmtGenericEnumerable() {
|
||||
exists(ValueOrRefType t | t = this.getIterableExpr().getType() |
|
||||
t.getABaseType*().getUnboundDeclaration() instanceof
|
||||
GenericCollections::SystemCollectionsGenericIEnumerableTInterface or
|
||||
t.(ArrayType).getRank() = 1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A class of foreach statements where the iterable expression
|
||||
* supports the use of the LINQ extension methods on `IEnumerable`.
|
||||
*/
|
||||
class ForeachStmtEnumerable extends ForeachStmt {
|
||||
ForeachStmtEnumerable() {
|
||||
exists(ValueOrRefType t | t = this.getIterableExpr().getType() |
|
||||
t.getABaseType*() instanceof Collections::SystemCollectionsIEnumerableInterface or
|
||||
t.(ArrayType).getRank() = 1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `foreach` statement `fes` could be converted to a `.All()` call.
|
||||
* That is, the `ForeachStmt` contains a single `if` with a condition that
|
||||
* accesses the loop variable and with a body that assigns `false` to a variable
|
||||
* and `break`s out of the `foreach`.
|
||||
*/
|
||||
predicate missedAllOpportunity(ForeachStmt fes) {
|
||||
predicate missedAllOpportunity(ForeachStmtGenericEnumerable fes) {
|
||||
exists(IfStmt is |
|
||||
// The loop contains an if statement with no else case, and nothing else.
|
||||
is = firstStmt(fes) and
|
||||
@@ -54,12 +83,12 @@ predicate missedAllOpportunity(ForeachStmt fes) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `foreach` statement `fes` could be converted to a `.Cast()` call.
|
||||
* Holds if the `foreach` statement `fes` can be converted to a `.Cast()` call.
|
||||
* That is, the loop variable is accessed only in the first statement of the
|
||||
* block, and the access is a cast. The first statement needs to be a
|
||||
* `LocalVariableDeclStmt`.
|
||||
* block, the access is a cast, and the first statement is a
|
||||
* local variable declaration statement `s`.
|
||||
*/
|
||||
predicate missedCastOpportunity(ForeachStmt fes, LocalVariableDeclStmt s) {
|
||||
predicate missedCastOpportunity(ForeachStmtEnumerable fes, LocalVariableDeclStmt s) {
|
||||
s = firstStmt(fes) and
|
||||
forex(VariableAccess va | va = fes.getVariable().getAnAccess() |
|
||||
va = s.getAVariableDeclExpr().getAChildExpr*()
|
||||
@@ -71,12 +100,12 @@ predicate missedCastOpportunity(ForeachStmt fes, LocalVariableDeclStmt s) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `foreach` statement `fes` could be converted to an `.OfType()` call.
|
||||
* Holds if `foreach` statement `fes` can be converted to an `.OfType()` call.
|
||||
* That is, the loop variable is accessed only in the first statement of the
|
||||
* block, and the access is a cast with the `as` operator. The first statement
|
||||
* needs to be a `LocalVariableDeclStmt`.
|
||||
* block, the access is a cast with the `as` operator, and the first statement
|
||||
* is a local variable declaration statement `s`.
|
||||
*/
|
||||
predicate missedOfTypeOpportunity(ForeachStmt fes, LocalVariableDeclStmt s) {
|
||||
predicate missedOfTypeOpportunity(ForeachStmtEnumerable fes, LocalVariableDeclStmt s) {
|
||||
s = firstStmt(fes) and
|
||||
forex(VariableAccess va | va = fes.getVariable().getAnAccess() |
|
||||
va = s.getAVariableDeclExpr().getAChildExpr*()
|
||||
@@ -88,12 +117,12 @@ predicate missedOfTypeOpportunity(ForeachStmt fes, LocalVariableDeclStmt s) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `foreach` statement `fes` could be converted to a `.Select()` call.
|
||||
* Holds if `foreach` statement `fes` can be converted to a `.Select()` call.
|
||||
* That is, the loop variable is accessed only in the first statement of the
|
||||
* block, and the access is not a cast. The first statement needs to be a
|
||||
* `LocalVariableDeclStmt`.
|
||||
* block, the access is not a cast, and the first statement is a
|
||||
* local variable declaration statement `s`.
|
||||
*/
|
||||
predicate missedSelectOpportunity(ForeachStmt fes, LocalVariableDeclStmt s) {
|
||||
predicate missedSelectOpportunity(ForeachStmtGenericEnumerable fes, LocalVariableDeclStmt s) {
|
||||
s = firstStmt(fes) and
|
||||
forex(VariableAccess va | va = fes.getVariable().getAnAccess() |
|
||||
va = s.getAVariableDeclExpr().getAChildExpr*()
|
||||
@@ -107,7 +136,7 @@ predicate missedSelectOpportunity(ForeachStmt fes, LocalVariableDeclStmt s) {
|
||||
* variable, and the body of the `if` is either a `continue` or there's nothing
|
||||
* else in the loop than the `if`.
|
||||
*/
|
||||
predicate missedWhereOpportunity(ForeachStmt fes, IfStmt is) {
|
||||
predicate missedWhereOpportunity(ForeachStmtGenericEnumerable fes, IfStmt is) {
|
||||
// The very first thing the foreach loop does is test its iteration variable.
|
||||
is = firstStmt(fes) and
|
||||
exists(VariableAccess va |
|
||||
|
||||
3
csharp/ql/lib/change-notes/released/0.7.2.md
Normal file
3
csharp/ql/lib/change-notes/released/0.7.2.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.7.2
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.7.1
|
||||
lastReleaseVersion: 0.7.2
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/csharp-all
|
||||
version: 0.7.2-dev
|
||||
version: 0.7.3-dev
|
||||
groups: csharp
|
||||
dbscheme: semmlecode.csharp.dbscheme
|
||||
extractor: csharp
|
||||
|
||||
@@ -861,6 +861,12 @@ class YieldReturnStmt extends YieldStmt {
|
||||
override string getAPrimaryQlClass() { result = "YieldReturnStmt" }
|
||||
}
|
||||
|
||||
bindingset[cfe1, cfe2]
|
||||
pragma[inline_late]
|
||||
private predicate sameCallable(ControlFlowElement cfe1, ControlFlowElement cfe2) {
|
||||
cfe1.getEnclosingCallable() = cfe2.getEnclosingCallable()
|
||||
}
|
||||
|
||||
/**
|
||||
* A `try` statement, for example
|
||||
*
|
||||
@@ -947,8 +953,7 @@ class TryStmt extends Stmt, @try_stmt {
|
||||
mid = this.getATriedElement() and
|
||||
not mid instanceof TryStmt and
|
||||
result = mid.getAChild() and
|
||||
pragma[only_bind_into](mid.getEnclosingCallable()) =
|
||||
pragma[only_bind_into](result.getEnclosingCallable())
|
||||
sameCallable(mid, result)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
## 0.7.2
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.7.1
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
* language-features
|
||||
*/
|
||||
|
||||
import csharp
|
||||
import Linq.Helpers
|
||||
|
||||
/*
|
||||
@@ -31,7 +30,7 @@ import Linq.Helpers
|
||||
* bool allEven = lst.All(i => i % 2 == 0);
|
||||
*/
|
||||
|
||||
from ForeachStmt fes
|
||||
from ForeachStmtGenericEnumerable fes
|
||||
where missedAllOpportunity(fes)
|
||||
select fes,
|
||||
"This foreach loop looks as if it might be testing whether every sequence element satisfies a predicate - consider using '.All(...)'."
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
import csharp
|
||||
import Linq.Helpers
|
||||
|
||||
from ForeachStmt fes, LocalVariableDeclStmt s
|
||||
from ForeachStmtEnumerable fes, LocalVariableDeclStmt s
|
||||
where missedCastOpportunity(fes, s)
|
||||
select fes,
|
||||
"This foreach loop immediately $@ - consider casting the sequence explicitly using '.Cast(...)'.",
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
import csharp
|
||||
import Linq.Helpers
|
||||
|
||||
from ForeachStmt fes, LocalVariableDeclStmt s
|
||||
from ForeachStmtEnumerable fes, LocalVariableDeclStmt s
|
||||
where missedOfTypeOpportunity(fes, s)
|
||||
select fes,
|
||||
"This foreach loop immediately uses 'as' to $@ - consider using '.OfType(...)' instead.", s,
|
||||
|
||||
@@ -20,7 +20,7 @@ predicate oversized(LocalVariableDeclStmt s) {
|
||||
)
|
||||
}
|
||||
|
||||
from ForeachStmt fes, LocalVariableDeclStmt s
|
||||
from ForeachStmtGenericEnumerable fes, LocalVariableDeclStmt s
|
||||
where
|
||||
missedSelectOpportunity(fes, s) and
|
||||
not oversized(s)
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
import csharp
|
||||
import Linq.Helpers
|
||||
|
||||
from ForeachStmt fes, IfStmt is
|
||||
from ForeachStmtGenericEnumerable fes, IfStmt is
|
||||
where
|
||||
missedWhereOpportunity(fes, is) and
|
||||
not missedAllOpportunity(fes)
|
||||
|
||||
3
csharp/ql/src/change-notes/released/0.7.2.md
Normal file
3
csharp/ql/src/change-notes/released/0.7.2.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.7.2
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.7.1
|
||||
lastReleaseVersion: 0.7.2
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/csharp-queries
|
||||
version: 0.7.2-dev
|
||||
version: 0.7.3-dev
|
||||
groups:
|
||||
- csharp
|
||||
- queries
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
class MissedCastOpportunity
|
||||
{
|
||||
public void M1(List<Animal> animals)
|
||||
{
|
||||
// BAD: Can be replaced with animals.Cast<Dog>().
|
||||
foreach (Animal a in animals)
|
||||
{
|
||||
Dog d = (Dog)a;
|
||||
d.Woof();
|
||||
}
|
||||
}
|
||||
|
||||
public void M2(NonEnumerableClass nec)
|
||||
{
|
||||
// GOOD: Not possible to use Linq here.
|
||||
foreach (Animal a in nec)
|
||||
{
|
||||
Dog d = (Dog)a;
|
||||
d.Woof();
|
||||
}
|
||||
}
|
||||
|
||||
public void M3(Animal[] animals)
|
||||
{
|
||||
// BAD: Can be replaced with animals.Cast<Dog>().
|
||||
foreach (Animal animal in animals)
|
||||
{
|
||||
Dog d = (Dog)animal;
|
||||
d.Woof();
|
||||
}
|
||||
}
|
||||
|
||||
public void M4(Array animals)
|
||||
{
|
||||
// BAD: Can be replaced with animals.Cast<Dog>().
|
||||
foreach (Animal animal in animals)
|
||||
{
|
||||
Dog d = (Dog)animal;
|
||||
d.Woof();
|
||||
}
|
||||
}
|
||||
|
||||
public void M5(IEnumerable animals)
|
||||
{
|
||||
// BAD: Can be replaced with animals.Cast<Dog>().
|
||||
foreach (object animal in animals)
|
||||
{
|
||||
Dog d = (Dog)animal;
|
||||
d.Woof();
|
||||
}
|
||||
}
|
||||
|
||||
public class NonEnumerableClass
|
||||
{
|
||||
public IEnumerator<Animal> GetEnumerator() => throw null;
|
||||
}
|
||||
|
||||
public class Animal { }
|
||||
|
||||
class Dog : Animal
|
||||
{
|
||||
private string name;
|
||||
|
||||
public Dog(string name)
|
||||
{
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void Woof()
|
||||
{
|
||||
Console.WriteLine("Woof! My name is " + name + ".");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
| MissedCastOpportunity.cs:10:9:14:9 | foreach (... ... in ...) ... | This foreach loop immediately $@ - consider casting the sequence explicitly using '.Cast(...)'. | MissedCastOpportunity.cs:12:13:12:27 | ... ...; | casts its iteration variable to another type |
|
||||
| MissedCastOpportunity.cs:30:9:34:9 | foreach (... ... in ...) ... | This foreach loop immediately $@ - consider casting the sequence explicitly using '.Cast(...)'. | MissedCastOpportunity.cs:32:13:32:32 | ... ...; | casts its iteration variable to another type |
|
||||
| MissedCastOpportunity.cs:40:9:44:9 | foreach (... ... in ...) ... | This foreach loop immediately $@ - consider casting the sequence explicitly using '.Cast(...)'. | MissedCastOpportunity.cs:42:13:42:32 | ... ...; | casts its iteration variable to another type |
|
||||
| MissedCastOpportunity.cs:50:9:54:9 | foreach (... ... in ...) ... | This foreach loop immediately $@ - consider casting the sequence explicitly using '.Cast(...)'. | MissedCastOpportunity.cs:52:13:52:32 | ... ...; | casts its iteration variable to another type |
|
||||
@@ -0,0 +1 @@
|
||||
Linq/MissedCastOpportunity.ql
|
||||
@@ -0,0 +1,2 @@
|
||||
semmle-extractor-options: /nostdlib /noconfig
|
||||
semmle-extractor-options: --load-sources-from-project:${testdir}/../../../resources/stubs/_frameworks/Microsoft.NETCore.App/Microsoft.NETCore.App.csproj
|
||||
@@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
class MissedWhereOpportunity
|
||||
{
|
||||
public void M1(List<int> lst)
|
||||
{
|
||||
// BAD: Can be replaced with lst.Where(e => e % 2 == 0)
|
||||
foreach (int i in lst)
|
||||
{
|
||||
if (i % 2 != 0)
|
||||
continue;
|
||||
Console.WriteLine(i);
|
||||
Console.WriteLine((i / 2));
|
||||
}
|
||||
|
||||
// BAD: Can be replaced with lst.Where(e => e % 2 == 0)
|
||||
foreach (int i in lst)
|
||||
{
|
||||
if (i % 2 == 0)
|
||||
{
|
||||
Console.WriteLine(i);
|
||||
Console.WriteLine((i / 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void M2(NonEnumerableClass nec)
|
||||
{
|
||||
// GOOD: Linq can't be used here.
|
||||
foreach (int i in nec)
|
||||
{
|
||||
if (i % 2 == 0)
|
||||
{
|
||||
Console.WriteLine(i);
|
||||
Console.WriteLine((i / 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void M3(int[] arr)
|
||||
{
|
||||
// BAD: Can be replaced with arr.Where(e => e % 2 == 0)
|
||||
foreach (var n in arr)
|
||||
{
|
||||
if (n % 2 == 0)
|
||||
{
|
||||
Console.WriteLine(n);
|
||||
Console.WriteLine((n / 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void M4(Array arr)
|
||||
{
|
||||
// GOOD: Linq can't be used here.
|
||||
foreach (var element in arr)
|
||||
{
|
||||
if (element.GetHashCode() % 2 == 0)
|
||||
{
|
||||
Console.WriteLine(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void M5(IEnumerable<int> elements)
|
||||
{
|
||||
// BAD: Can be replaced with elements.Where(e => e.GetHashCode() % 2 == 0)
|
||||
foreach (var element in elements)
|
||||
{
|
||||
if (element.GetHashCode() % 2 == 0)
|
||||
{
|
||||
Console.WriteLine(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class NonEnumerableClass
|
||||
{
|
||||
public IEnumerator<int> GetEnumerator() => throw null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
| MissedWhereOpportunity.cs:10:9:16:9 | foreach (... ... in ...) ... | This foreach loop $@ - consider filtering the sequence explicitly using '.Where(...)'. | MissedWhereOpportunity.cs:12:17:12:26 | ... != ... | implicitly filters its target sequence |
|
||||
| MissedWhereOpportunity.cs:19:9:26:9 | foreach (... ... in ...) ... | This foreach loop $@ - consider filtering the sequence explicitly using '.Where(...)'. | MissedWhereOpportunity.cs:21:17:21:26 | ... == ... | implicitly filters its target sequence |
|
||||
| MissedWhereOpportunity.cs:45:9:52:9 | foreach (... ... in ...) ... | This foreach loop $@ - consider filtering the sequence explicitly using '.Where(...)'. | MissedWhereOpportunity.cs:47:17:47:26 | ... == ... | implicitly filters its target sequence |
|
||||
| MissedWhereOpportunity.cs:70:9:76:9 | foreach (... ... in ...) ... | This foreach loop $@ - consider filtering the sequence explicitly using '.Where(...)'. | MissedWhereOpportunity.cs:72:17:72:46 | ... == ... | implicitly filters its target sequence |
|
||||
@@ -0,0 +1 @@
|
||||
Linq/MissedWhereOpportunity.ql
|
||||
@@ -0,0 +1,2 @@
|
||||
semmle-extractor-options: /nostdlib /noconfig
|
||||
semmle-extractor-options: --load-sources-from-project:${testdir}/../../../resources/stubs/_frameworks/Microsoft.NETCore.App/Microsoft.NETCore.App.csproj
|
||||
@@ -397,6 +397,8 @@ The following components are supported:
|
||||
- **SyntheticGlobal[**\ `name`\ **]** selects the synthetic global with name `name`.
|
||||
- **ArrayElement** selects the elements of an array.
|
||||
- **Element** selects the elements of a collection-like container.
|
||||
- **WithoutElement** selects a collection-like container without its elements. This is for input only.
|
||||
- **WithElement** selects the elements of a collection-like container, but points to the container itself. This is for input only.
|
||||
- **MapKey** selects the element keys of a map.
|
||||
- **MapValue** selects the element values of a map.
|
||||
|
||||
|
||||
@@ -122,12 +122,19 @@ Global environments
|
||||
|
||||
The global module environment has a single entry ``QlBuiltins``.
|
||||
|
||||
The global type environment has entries for the primitive types ``int``, ``float``, ``string``, ``boolean``, and ``date``, as well as any types defined in the database schema.
|
||||
The global type environment has entries for the primitive types ``int``, ``float``, ``string``, ``boolean``, and ``date``.
|
||||
|
||||
The global predicate environment includes all the built-in classless predicates, as well as any extensional predicates declared in the database schema.
|
||||
The global predicate environment includes all the built-in classless predicates.
|
||||
|
||||
The three global signature environments are empty.
|
||||
|
||||
Database schema environments
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The database schema type environment has entries for types declared in the database schema.
|
||||
|
||||
The database schema predicate environment has entries for extensional predicates declared in the database schema.
|
||||
|
||||
The program is invalid if any of these environments is not definite.
|
||||
|
||||
Module environments
|
||||
@@ -146,7 +153,7 @@ These are defined as follows (with X denoting the type of entity we are currentl
|
||||
|
||||
2. for each module which the current module directly imports (excluding ``private`` imports - see "`Import directives <#import-directives>`__"): all entries from the *exported X environment* that have a key not present in the *publically declared X environment* of the current module, and
|
||||
|
||||
3. if X is ``predicates``, then for each module signature ``S`` that is implemented by the current module: an entry for each module signature default predicate in ``S`` that does not have the same name and arity as any of the entries in the **publically declared predicate environment** of the current module.
|
||||
3. if X is ``predicate``, then for each module signature ``S`` that is implemented by the current module: an entry for each module signature default predicate in ``S`` that does not have the same name and arity as any of the entries in the **publically declared predicate environment** of the current module.
|
||||
|
||||
- The *visible X environment* of a module is the union of
|
||||
|
||||
@@ -160,7 +167,9 @@ These are defined as follows (with X denoting the type of entity we are currentl
|
||||
|
||||
5. if there is an enclosing module: all entries from the *visible X environment* of the enclosing module that have a key not present in the *publically declared X environment* of the current module, and
|
||||
|
||||
6. all parameters of the current module that are of type X.
|
||||
6. if there is no enclosing module and X is either ``type`` or ``predicate``: all entries from the *database schema X environment* that have a key not present in the *publically declared X environment* of the current module, and
|
||||
|
||||
7. all parameters of the current module that are of type X.
|
||||
|
||||
The program is invalid if any of these environments is not definite.
|
||||
|
||||
|
||||
@@ -310,3 +310,4 @@ and the CodeQL library pack ``codeql/swift-all`` (`changelog <https://github.com
|
||||
`SQLite3 <https://sqlite.org/index.html>`__, Database
|
||||
`SQLite.swift <https://github.com/stephencelis/SQLite.swift>`__, Database
|
||||
`WebKit <https://developer.apple.com/documentation/webkit>`__, User interface library
|
||||
`UIKit <https://developer.apple.com/documentation/uikit>`__, User interface library
|
||||
|
||||
@@ -773,7 +773,7 @@ func installDependenciesAndBuild() {
|
||||
|
||||
goModVersion, goModVersionFound := tryReadGoDirective(buildInfo)
|
||||
|
||||
if goModVersionFound && semver.Compare("v"+goModVersion, getEnvGoSemVer()) >= 0 {
|
||||
if goModVersionFound && semver.Compare("v"+goModVersion, getEnvGoSemVer()) > 0 {
|
||||
diagnostics.EmitNewerGoVersionNeeded()
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@ module github.com/github/codeql-go
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
golang.org/x/mod v0.8.0
|
||||
golang.org/x/tools v0.6.0
|
||||
golang.org/x/mod v0.12.0
|
||||
golang.org/x/tools v0.11.1
|
||||
)
|
||||
|
||||
require (
|
||||
golang.org/x/sys v0.5.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
golang.org/x/sys v0.10.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
)
|
||||
|
||||
@@ -8,6 +8,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVD
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
@@ -23,6 +25,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9w
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@@ -34,7 +38,11 @@ golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.11.1 h1:ojD5zOW8+7dOGzdnNgersm8aPfcDjhMp12UfG93NIMc=
|
||||
golang.org/x/tools v0.11.1/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
## 0.6.2
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Logrus' `WithContext` methods are no longer treated as if they output the values stored in that context to a log message.
|
||||
|
||||
## 0.6.1
|
||||
|
||||
### New Features
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
## 0.6.2
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Logrus' `WithContext` methods are no longer treated as if they output the values stored in that context to a log message.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.6.1
|
||||
lastReleaseVersion: 0.6.2
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/go-all
|
||||
version: 0.6.2-dev
|
||||
version: 0.6.3-dev
|
||||
groups: go
|
||||
dbscheme: go.dbscheme
|
||||
extractor: go
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
*/
|
||||
|
||||
import go
|
||||
private import semmle.go.dataflow.DataFlowForStringsNewReplacer
|
||||
|
||||
/** Provides predicates and classes for working with string operations. */
|
||||
module StringOps {
|
||||
@@ -223,20 +222,10 @@ module StringOps {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A configuration for tracking flow from a call to `strings.NewReplacer` to
|
||||
* the receiver of a call to `strings.Replacer.Replace` or
|
||||
* `strings.Replacer.WriteString`.
|
||||
*/
|
||||
private class StringsNewReplacerConfiguration extends DataFlowForStringsNewReplacer::Configuration
|
||||
{
|
||||
StringsNewReplacerConfiguration() { this = "StringsNewReplacerConfiguration" }
|
||||
private module StringsNewReplacerConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof StringsNewReplacerCall }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
source instanceof StringsNewReplacerCall
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
predicate isSink(DataFlow::Node sink) {
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
sink = call.getReceiver() and
|
||||
call.getTarget().hasQualifiedName("strings", "Replacer", ["Replace", "WriteString"])
|
||||
@@ -244,6 +233,12 @@ module StringOps {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks data flow from a call to `strings.NewReplacer` to the receiver of
|
||||
* a call to `strings.Replacer.Replace` or `strings.Replacer.WriteString`.
|
||||
*/
|
||||
private module StringsNewReplacerFlow = DataFlow::Global<StringsNewReplacerConfig>;
|
||||
|
||||
/**
|
||||
* A call to `strings.Replacer.Replace` or `strings.Replacer.WriteString`.
|
||||
*/
|
||||
@@ -251,11 +246,8 @@ module StringOps {
|
||||
string replacedString;
|
||||
|
||||
StringsReplacerReplaceOrWriteString() {
|
||||
exists(
|
||||
StringsNewReplacerConfiguration config, StringsNewReplacerCall source,
|
||||
DataFlow::Node sink, DataFlow::MethodCallNode call
|
||||
|
|
||||
config.hasFlow(source, sink) and
|
||||
exists(StringsNewReplacerCall source, DataFlow::Node sink, DataFlow::MethodCallNode call |
|
||||
StringsNewReplacerFlow::flow(source, sink) and
|
||||
sink = call.getReceiver() and
|
||||
replacedString = source.getAReplacedArgument().getStringValue() and
|
||||
(
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
/**
|
||||
* Provides a library for local (intra-procedural) and global (inter-procedural)
|
||||
* data flow analysis: deciding whether data can flow from a _source_ to a
|
||||
* _sink_.
|
||||
*
|
||||
* Unless configured otherwise, _flow_ means that the exact value of
|
||||
* the source may reach the sink. We do not track flow across pointer
|
||||
* dereferences or array indexing. To track these types of flow, where the
|
||||
* exact value may not be preserved, import
|
||||
* `semmle.code.go.dataflow.TaintTracking`.
|
||||
*
|
||||
* To use global (interprocedural) data flow, extend the class
|
||||
* `DataFlow::Configuration` as documented on that class. To use local
|
||||
* (intraprocedural) data flow, invoke `DataFlow::localFlow` or
|
||||
* `DataFlow::LocalFlowStep` with arguments of type `DataFlow::Node`.
|
||||
*/
|
||||
|
||||
import go
|
||||
|
||||
/**
|
||||
* Provides a library for local (intra-procedural) and global (inter-procedural)
|
||||
* data flow analysis.
|
||||
*/
|
||||
module DataFlowForStringsNewReplacer {
|
||||
import semmle.go.dataflow.internal.DataFlowImplForStringsNewReplacer
|
||||
import Properties
|
||||
}
|
||||
@@ -1,402 +0,0 @@
|
||||
/**
|
||||
* DEPRECATED: Use `Global` and `GlobalWithState` instead.
|
||||
*
|
||||
* Provides a `Configuration` class backwards-compatible interface to the data
|
||||
* flow library.
|
||||
*/
|
||||
|
||||
private import DataFlowImplCommon
|
||||
private import DataFlowImplSpecific::Private
|
||||
import DataFlowImplSpecific::Public
|
||||
private import DataFlowImpl
|
||||
import DataFlowImplCommonPublic
|
||||
import FlowStateString
|
||||
private import codeql.util.Unit
|
||||
|
||||
/**
|
||||
* A configuration of interprocedural data flow analysis. This defines
|
||||
* sources, sinks, and any other configurable aspect of the analysis. Each
|
||||
* use of the global data flow library must define its own unique extension
|
||||
* of this abstract class. To create a configuration, extend this class with
|
||||
* a subclass whose characteristic predicate is a unique singleton string.
|
||||
* For example, write
|
||||
*
|
||||
* ```ql
|
||||
* class MyAnalysisConfiguration extends DataFlow::Configuration {
|
||||
* MyAnalysisConfiguration() { this = "MyAnalysisConfiguration" }
|
||||
* // Override `isSource` and `isSink`.
|
||||
* // Optionally override `isBarrier`.
|
||||
* // Optionally override `isAdditionalFlowStep`.
|
||||
* }
|
||||
* ```
|
||||
* Conceptually, this defines a graph where the nodes are `DataFlow::Node`s and
|
||||
* the edges are those data-flow steps that preserve the value of the node
|
||||
* along with any additional edges defined by `isAdditionalFlowStep`.
|
||||
* Specifying nodes in `isBarrier` will remove those nodes from the graph, and
|
||||
* specifying nodes in `isBarrierIn` and/or `isBarrierOut` will remove in-going
|
||||
* and/or out-going edges from those nodes, respectively.
|
||||
*
|
||||
* Then, to query whether there is flow between some `source` and `sink`,
|
||||
* write
|
||||
*
|
||||
* ```ql
|
||||
* exists(MyAnalysisConfiguration cfg | cfg.hasFlow(source, sink))
|
||||
* ```
|
||||
*
|
||||
* Multiple configurations can coexist, but two classes extending
|
||||
* `DataFlow::Configuration` should never depend on each other. One of them
|
||||
* should instead depend on a `DataFlow2::Configuration`, a
|
||||
* `DataFlow3::Configuration`, or a `DataFlow4::Configuration`.
|
||||
*/
|
||||
abstract class Configuration extends string {
|
||||
bindingset[this]
|
||||
Configuration() { any() }
|
||||
|
||||
/**
|
||||
* Holds if `source` is a relevant data flow source.
|
||||
*/
|
||||
predicate isSource(Node source) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `source` is a relevant data flow source with the given initial
|
||||
* `state`.
|
||||
*/
|
||||
predicate isSource(Node source, FlowState state) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `sink` is a relevant data flow sink.
|
||||
*/
|
||||
predicate isSink(Node sink) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `sink` is a relevant data flow sink accepting `state`.
|
||||
*/
|
||||
predicate isSink(Node sink, FlowState state) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data flow through `node` is prohibited. This completely removes
|
||||
* `node` from the data flow graph.
|
||||
*/
|
||||
predicate isBarrier(Node node) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data flow through `node` is prohibited when the flow state is
|
||||
* `state`.
|
||||
*/
|
||||
predicate isBarrier(Node node, FlowState state) { none() }
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
predicate isBarrierIn(Node node) { none() }
|
||||
|
||||
/** Holds if data flow out of `node` is prohibited. */
|
||||
predicate isBarrierOut(Node node) { none() }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead.
|
||||
*
|
||||
* Holds if data flow through nodes guarded by `guard` is prohibited.
|
||||
*/
|
||||
deprecated predicate isBarrierGuard(BarrierGuard guard) { none() }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead.
|
||||
*
|
||||
* Holds if data flow through nodes guarded by `guard` is prohibited when
|
||||
* the flow state is `state`
|
||||
*/
|
||||
deprecated predicate isBarrierGuard(BarrierGuard guard, FlowState state) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps.
|
||||
*/
|
||||
predicate isAdditionalFlowStep(Node node1, Node node2) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps.
|
||||
* This step is only applicable in `state1` and updates the flow state to `state2`.
|
||||
*/
|
||||
predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) {
|
||||
none()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if an arbitrary number of implicit read steps of content `c` may be
|
||||
* taken at `node`.
|
||||
*/
|
||||
predicate allowImplicitRead(Node node, ContentSet c) { none() }
|
||||
|
||||
/**
|
||||
* Gets the virtual dispatch branching limit when calculating field flow.
|
||||
* This can be overridden to a smaller value to improve performance (a
|
||||
* value of 0 disables field flow), or a larger value to get more results.
|
||||
*/
|
||||
int fieldFlowBranchLimit() { result = 2 }
|
||||
|
||||
/**
|
||||
* Gets a data flow configuration feature to add restrictions to the set of
|
||||
* valid flow paths.
|
||||
*
|
||||
* - `FeatureHasSourceCallContext`:
|
||||
* Assume that sources have some existing call context to disallow
|
||||
* conflicting return-flow directly following the source.
|
||||
* - `FeatureHasSinkCallContext`:
|
||||
* Assume that sinks have some existing call context to disallow
|
||||
* conflicting argument-to-parameter flow directly preceding the sink.
|
||||
* - `FeatureEqualSourceSinkCallContext`:
|
||||
* Implies both of the above and additionally ensures that the entire flow
|
||||
* path preserves the call context.
|
||||
*
|
||||
* These features are generally not relevant for typical end-to-end data flow
|
||||
* queries, but should only be used for constructing paths that need to
|
||||
* somehow be pluggable in another path context.
|
||||
*/
|
||||
FlowFeature getAFeature() { none() }
|
||||
|
||||
/** Holds if sources should be grouped in the result of `hasFlowPath`. */
|
||||
predicate sourceGrouping(Node source, string sourceGroup) { none() }
|
||||
|
||||
/** Holds if sinks should be grouped in the result of `hasFlowPath`. */
|
||||
predicate sinkGrouping(Node sink, string sinkGroup) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `source` to `sink` for this configuration.
|
||||
*/
|
||||
predicate hasFlow(Node source, Node sink) { hasFlow(source, sink, this) }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `source` to `sink` for this configuration.
|
||||
*
|
||||
* The corresponding paths are generated from the end-points and the graph
|
||||
* included in the module `PathGraph`.
|
||||
*/
|
||||
predicate hasFlowPath(PathNode source, PathNode sink) { hasFlowPath(source, sink, this) }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from some source to `sink` for this configuration.
|
||||
*/
|
||||
predicate hasFlowTo(Node sink) { hasFlowTo(sink, this) }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from some source to `sink` for this configuration.
|
||||
*/
|
||||
predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `FlowExploration<explorationLimit>` instead.
|
||||
*
|
||||
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
|
||||
* measured in approximate number of interprocedural steps.
|
||||
*/
|
||||
deprecated int explorationLimit() { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
* This feature should only be used for debugging or when the data flow graph
|
||||
* is not visualized (for example in a `path-problem` query).
|
||||
*/
|
||||
predicate includeHiddenNodes() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* This class exists to prevent mutual recursion between the user-overridden
|
||||
* member predicates of `Configuration` and the rest of the data-flow library.
|
||||
* Good performance cannot be guaranteed in the presence of such recursion, so
|
||||
* it should be replaced by using more than one copy of the data flow library.
|
||||
*/
|
||||
abstract private class ConfigurationRecursionPrevention extends Configuration {
|
||||
bindingset[this]
|
||||
ConfigurationRecursionPrevention() { any() }
|
||||
|
||||
override predicate hasFlow(Node source, Node sink) {
|
||||
strictcount(Node n | this.isSource(n)) < 0
|
||||
or
|
||||
strictcount(Node n | this.isSource(n, _)) < 0
|
||||
or
|
||||
strictcount(Node n | this.isSink(n)) < 0
|
||||
or
|
||||
strictcount(Node n | this.isSink(n, _)) < 0
|
||||
or
|
||||
strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0
|
||||
or
|
||||
strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, _, n2, _)) < 0
|
||||
or
|
||||
super.hasFlow(source, sink)
|
||||
}
|
||||
}
|
||||
|
||||
/** A bridge class to access the deprecated `isBarrierGuard`. */
|
||||
private class BarrierGuardGuardedNodeBridge extends Unit {
|
||||
abstract predicate guardedNode(Node n, Configuration config);
|
||||
|
||||
abstract predicate guardedNode(Node n, FlowState state, Configuration config);
|
||||
}
|
||||
|
||||
private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge {
|
||||
deprecated override predicate guardedNode(Node n, Configuration config) {
|
||||
exists(BarrierGuard g |
|
||||
config.isBarrierGuard(g) and
|
||||
n = g.getAGuardedNode()
|
||||
)
|
||||
}
|
||||
|
||||
deprecated override predicate guardedNode(Node n, FlowState state, Configuration config) {
|
||||
exists(BarrierGuard g |
|
||||
config.isBarrierGuard(g, state) and
|
||||
n = g.getAGuardedNode()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private FlowState relevantState(Configuration config) {
|
||||
config.isSource(_, result) or
|
||||
config.isSink(_, result) or
|
||||
config.isBarrier(_, result) or
|
||||
config.isAdditionalFlowStep(_, result, _, _) or
|
||||
config.isAdditionalFlowStep(_, _, _, result)
|
||||
}
|
||||
|
||||
private newtype TConfigState =
|
||||
TMkConfigState(Configuration config, FlowState state) {
|
||||
state = relevantState(config) or state instanceof FlowStateEmpty
|
||||
}
|
||||
|
||||
private Configuration getConfig(TConfigState state) { state = TMkConfigState(result, _) }
|
||||
|
||||
private FlowState getState(TConfigState state) { state = TMkConfigState(_, result) }
|
||||
|
||||
private predicate singleConfiguration() { 1 = strictcount(Configuration c) }
|
||||
|
||||
private module Config implements FullStateConfigSig {
|
||||
class FlowState = TConfigState;
|
||||
|
||||
predicate isSource(Node source, FlowState state) {
|
||||
getConfig(state).isSource(source, getState(state))
|
||||
or
|
||||
getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty
|
||||
}
|
||||
|
||||
predicate isSink(Node sink) { none() }
|
||||
|
||||
predicate isSink(Node sink, FlowState state) {
|
||||
getConfig(state).isSink(sink, getState(state))
|
||||
or
|
||||
getConfig(state).isSink(sink) and getState(state) instanceof FlowStateEmpty
|
||||
}
|
||||
|
||||
predicate isBarrier(Node node) { none() }
|
||||
|
||||
predicate isBarrier(Node node, FlowState state) {
|
||||
getConfig(state).isBarrier(node, getState(state)) or
|
||||
getConfig(state).isBarrier(node) or
|
||||
any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getState(state), getConfig(state)) or
|
||||
any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getConfig(state))
|
||||
}
|
||||
|
||||
predicate isBarrierIn(Node node) { any(Configuration config).isBarrierIn(node) }
|
||||
|
||||
predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) }
|
||||
|
||||
predicate isAdditionalFlowStep(Node node1, Node node2) {
|
||||
singleConfiguration() and
|
||||
any(Configuration config).isAdditionalFlowStep(node1, node2)
|
||||
}
|
||||
|
||||
predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) {
|
||||
getConfig(state1).isAdditionalFlowStep(node1, getState(state1), node2, getState(state2)) and
|
||||
getConfig(state2) = getConfig(state1)
|
||||
or
|
||||
not singleConfiguration() and
|
||||
getConfig(state1).isAdditionalFlowStep(node1, node2) and
|
||||
state2 = state1
|
||||
}
|
||||
|
||||
predicate allowImplicitRead(Node node, ContentSet c) {
|
||||
any(Configuration config).allowImplicitRead(node, c)
|
||||
}
|
||||
|
||||
predicate neverSkip(Node node) { none() }
|
||||
|
||||
int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) }
|
||||
|
||||
FlowFeature getAFeature() { result = any(Configuration config).getAFeature() }
|
||||
|
||||
predicate sourceGrouping(Node source, string sourceGroup) {
|
||||
any(Configuration config).sourceGrouping(source, sourceGroup)
|
||||
}
|
||||
|
||||
predicate sinkGrouping(Node sink, string sinkGroup) {
|
||||
any(Configuration config).sinkGrouping(sink, sinkGroup)
|
||||
}
|
||||
|
||||
predicate includeHiddenNodes() { any(Configuration config).includeHiddenNodes() }
|
||||
}
|
||||
|
||||
private import Impl<Config> as I
|
||||
|
||||
/**
|
||||
* A `Node` augmented with a call context (except for sinks), an access path, and a configuration.
|
||||
* Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated.
|
||||
*/
|
||||
class PathNode instanceof I::PathNode {
|
||||
/** Gets a textual representation of this element. */
|
||||
final string toString() { result = super.toString() }
|
||||
|
||||
/**
|
||||
* Gets a textual representation of this element, including a textual
|
||||
* representation of the call context.
|
||||
*/
|
||||
final string toStringWithContext() { result = super.toStringWithContext() }
|
||||
|
||||
/**
|
||||
* Holds if this element is at the specified location.
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `filepath`.
|
||||
* For more information, see
|
||||
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
final predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
}
|
||||
|
||||
/** Gets the underlying `Node`. */
|
||||
final Node getNode() { result = super.getNode() }
|
||||
|
||||
/** Gets the `FlowState` of this node. */
|
||||
final FlowState getState() { result = getState(super.getState()) }
|
||||
|
||||
/** Gets the associated configuration. */
|
||||
final Configuration getConfiguration() { result = getConfig(super.getState()) }
|
||||
|
||||
/** Gets a successor of this node, if any. */
|
||||
final PathNode getASuccessor() { result = super.getASuccessor() }
|
||||
|
||||
/** Holds if this node is a source. */
|
||||
final predicate isSource() { super.isSource() }
|
||||
|
||||
/** Holds if this node is a grouping of source nodes. */
|
||||
final predicate isSourceGroup(string group) { super.isSourceGroup(group) }
|
||||
|
||||
/** Holds if this node is a grouping of sink nodes. */
|
||||
final predicate isSinkGroup(string group) { super.isSinkGroup(group) }
|
||||
}
|
||||
|
||||
module PathGraph = I::PathGraph;
|
||||
|
||||
private predicate hasFlow(Node source, Node sink, Configuration config) {
|
||||
exists(PathNode source0, PathNode sink0 |
|
||||
hasFlowPath(source0, sink0, config) and
|
||||
source0.getNode() = source and
|
||||
sink0.getNode() = sink
|
||||
)
|
||||
}
|
||||
|
||||
private predicate hasFlowPath(PathNode source, PathNode sink, Configuration config) {
|
||||
I::flowPath(source, sink) and source.getConfiguration() = config
|
||||
}
|
||||
|
||||
private predicate hasFlowTo(Node sink, Configuration config) { hasFlow(_, sink, config) }
|
||||
|
||||
predicate flowsTo = hasFlow/3;
|
||||
@@ -14,9 +14,11 @@ module AllocationSizeOverflow {
|
||||
import AllocationSizeOverflowCustomizations::AllocationSizeOverflow
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use copies of `FindLargeLensConfig` and `FindLargeLensFlow` instead.
|
||||
*
|
||||
* A taint-tracking configuration for identifying `len(...)` calls whose argument may be large.
|
||||
*/
|
||||
class FindLargeLensConfiguration extends TaintTracking2::Configuration {
|
||||
deprecated class FindLargeLensConfiguration extends TaintTracking2::Configuration {
|
||||
FindLargeLensConfiguration() { this = "AllocationSizeOverflow::FindLargeLens" }
|
||||
|
||||
override predicate isSource(DataFlow::Node nd) { nd instanceof Source }
|
||||
@@ -30,16 +32,31 @@ module AllocationSizeOverflow {
|
||||
override predicate isSanitizer(DataFlow::Node nd) { nd instanceof Sanitizer }
|
||||
}
|
||||
|
||||
private module FindLargeLensConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node nd) { nd instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node nd) { nd = Builtin::len().getACall().getArgument(0) }
|
||||
|
||||
predicate isBarrier(DataFlow::Node nd) { nd instanceof Sanitizer }
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks taint flow to find `len(...)` calls whose argument may be large.
|
||||
*/
|
||||
private module FindLargeLensFlow = TaintTracking::Global<FindLargeLensConfig>;
|
||||
|
||||
private DataFlow::CallNode getALargeLenCall() {
|
||||
exists(FindLargeLensConfiguration config, DataFlow::Node lenArg | config.hasFlow(_, lenArg) |
|
||||
exists(DataFlow::Node lenArg | FindLargeLensFlow::flow(_, lenArg) |
|
||||
result.getArgument(0) = lenArg
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `Flow` instead.
|
||||
*
|
||||
* A taint-tracking configuration for identifying allocation-size overflows.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "AllocationSizeOverflow" }
|
||||
|
||||
override predicate isSource(DataFlow::Node nd) { nd instanceof Source }
|
||||
@@ -70,4 +87,33 @@ module AllocationSizeOverflow {
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node nd) { nd instanceof Sanitizer }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `nd` is at a position where overflow might occur, and its result is used to compute
|
||||
* allocation size `allocsz`.
|
||||
*/
|
||||
predicate isSinkWithAllocationSize(DataFlow::Node nd, DataFlow::Node allocsz) {
|
||||
nd.(Sink).getAllocationSize() = allocsz
|
||||
}
|
||||
|
||||
private module Config implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { isSinkWithAllocationSize(sink, _) }
|
||||
|
||||
predicate isBarrier(DataFlow::Node nd) { nd instanceof Sanitizer }
|
||||
|
||||
predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
additionalStep(pred, succ)
|
||||
or
|
||||
exists(DataFlow::CallNode c |
|
||||
c = getALargeLenCall() and
|
||||
pred = c.getArgument(0) and
|
||||
succ = c
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Tracks taint flow to find allocation-size overflows. */
|
||||
module Flow = TaintTracking::Global<Config>;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ module CleartextLogging {
|
||||
import CleartextLoggingCustomizations::CleartextLogging
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `Flow` instead.
|
||||
*
|
||||
* A data-flow tracking configuration for clear-text logging of sensitive information.
|
||||
*
|
||||
* This configuration identifies flows from `Source`s, which are sources of
|
||||
@@ -25,7 +27,7 @@ module CleartextLogging {
|
||||
* added either by extending the relevant class, or by subclassing this configuration itself,
|
||||
* and amending the sources and sinks.
|
||||
*/
|
||||
class Configuration extends DataFlow::Configuration {
|
||||
deprecated class Configuration extends DataFlow::Configuration {
|
||||
Configuration() { this = "CleartextLogging" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -56,4 +58,43 @@ module CleartextLogging {
|
||||
not any(Protobuf::GetMethod gm).taintStep(src, trg)
|
||||
}
|
||||
}
|
||||
|
||||
private module Config implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) {
|
||||
node instanceof Barrier
|
||||
or
|
||||
exists(DataFlow::CallNode call | node = call.getResult() |
|
||||
call.getTarget() = Builtin::error().getType().getMethod("Error")
|
||||
or
|
||||
call.getTarget().(Method).hasQualifiedName("fmt", "Stringer", "String")
|
||||
)
|
||||
}
|
||||
|
||||
predicate isAdditionalFlowStep(DataFlow::Node src, DataFlow::Node trg) {
|
||||
// A taint propagating data-flow edge through structs: a tainted write taints the entire struct.
|
||||
exists(Write write |
|
||||
write.writesField(trg.(DataFlow::PostUpdateNode).getPreUpdateNode(), _, src)
|
||||
)
|
||||
or
|
||||
// taint steps that do not include flow through fields. Field reads would produce FPs due to
|
||||
// the additional taint step above that taints whole structs from individual field writes.
|
||||
TaintTracking::localTaintStep(src, trg) and
|
||||
not TaintTracking::fieldReadStep(src, trg) and
|
||||
// Also exclude protobuf field fetches, since they amount to single field reads.
|
||||
not any(Protobuf::GetMethod gm).taintStep(src, trg)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks data flow for reasoning about clear-text logging of sensitive
|
||||
* information, from `Source`s, which are sources of sensitive data, to
|
||||
* `Sink`s, which is an abstract class representing all the places sensitive
|
||||
* data may be stored in cleartext. Additional sources or sinks can be added
|
||||
* by extending the relevant class.
|
||||
*/
|
||||
module Flow = DataFlow::Global<Config>;
|
||||
}
|
||||
|
||||
@@ -17,10 +17,12 @@ module CommandInjection {
|
||||
import CommandInjectionCustomizations::CommandInjection
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `Flow` instead.
|
||||
*
|
||||
* A taint-tracking configuration for reasoning about command-injection vulnerabilities
|
||||
* with sinks which are not sanitized by `--`.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "CommandInjection" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -39,6 +41,22 @@ module CommandInjection {
|
||||
}
|
||||
}
|
||||
|
||||
private module Config implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) {
|
||||
exists(Sink s | sink = s | not s.doubleDashIsSanitizing())
|
||||
}
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks taint flow for reasoning about command-injection vulnerabilities
|
||||
* with sinks which are not sanitized by `--`.
|
||||
*/
|
||||
module Flow = TaintTracking::Global<Config>;
|
||||
|
||||
private class ArgumentArrayWithDoubleDash extends DataFlow::Node {
|
||||
int doubleDashIndex;
|
||||
|
||||
@@ -79,10 +97,12 @@ module CommandInjection {
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `DoubleDashSanitizingFlow` instead.
|
||||
*
|
||||
* A taint-tracking configuration for reasoning about command-injection vulnerabilities
|
||||
* with sinks which are sanitized by `--`.
|
||||
*/
|
||||
class DoubleDashSanitizingConfiguration extends TaintTracking::Configuration {
|
||||
deprecated class DoubleDashSanitizingConfiguration extends TaintTracking::Configuration {
|
||||
DoubleDashSanitizingConfiguration() { this = "CommandInjectionWithDoubleDashSanitizer" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -101,4 +121,21 @@ module CommandInjection {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
private module DoubleDashSanitizingConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { exists(Sink s | sink = s | s.doubleDashIsSanitizing()) }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) {
|
||||
node instanceof Sanitizer or
|
||||
node = any(ArgumentArrayWithDoubleDash array).getASanitizedElement()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks taint flow for reasoning about command-injection vulnerabilities
|
||||
* with sinks which are sanitized by `--`.
|
||||
*/
|
||||
module DoubleDashSanitizingFlow = TaintTracking::Global<DoubleDashSanitizingConfig>;
|
||||
}
|
||||
|
||||
@@ -188,8 +188,12 @@ class UnknownExternalApiDataNode extends ExternalApiDataNode {
|
||||
/** DEPRECATED: Alias for UnknownExternalApiDataNode */
|
||||
deprecated class UnknownExternalAPIDataNode = UnknownExternalApiDataNode;
|
||||
|
||||
/** A configuration for tracking flow from `RemoteFlowSource`s to `ExternalApiDataNode`s. */
|
||||
class UntrustedDataToExternalApiConfig extends TaintTracking::Configuration {
|
||||
/**
|
||||
* DEPRECATED: Use `UntrustedDataToExternalApiFlow` instead.
|
||||
*
|
||||
* A configuration for tracking flow from `RemoteFlowSource`s to `ExternalApiDataNode`s.
|
||||
*/
|
||||
deprecated class UntrustedDataToExternalApiConfig extends TaintTracking::Configuration {
|
||||
UntrustedDataToExternalApiConfig() { this = "UntrustedDataToExternalAPIConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof UntrustedFlowSource }
|
||||
@@ -197,11 +201,26 @@ class UntrustedDataToExternalApiConfig extends TaintTracking::Configuration {
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof ExternalApiDataNode }
|
||||
}
|
||||
|
||||
private module UntrustedDataConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof UntrustedFlowSource }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof ExternalApiDataNode }
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks data flow from `RemoteFlowSource`s to `ExternalApiDataNode`s.
|
||||
*/
|
||||
module UntrustedDataToExternalApiFlow = DataFlow::Global<UntrustedDataConfig>;
|
||||
|
||||
/** DEPRECATED: Alias for UntrustedDataToExternalApiConfig */
|
||||
deprecated class UntrustedDataToExternalAPIConfig = UntrustedDataToExternalApiConfig;
|
||||
|
||||
/** A configuration for tracking flow from `RemoteFlowSource`s to `UnknownExternalApiDataNode`s. */
|
||||
class UntrustedDataToUnknownExternalApiConfig extends TaintTracking::Configuration {
|
||||
/**
|
||||
* DEPRECATED: Use `UntrustedDataToUnknownExternalApiFlow` instead.
|
||||
*
|
||||
* A configuration for tracking flow from `RemoteFlowSource`s to `UnknownExternalApiDataNode`s.
|
||||
*/
|
||||
deprecated class UntrustedDataToUnknownExternalApiConfig extends TaintTracking::Configuration {
|
||||
UntrustedDataToUnknownExternalApiConfig() { this = "UntrustedDataToUnknownExternalAPIConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof UntrustedFlowSource }
|
||||
@@ -212,14 +231,24 @@ class UntrustedDataToUnknownExternalApiConfig extends TaintTracking::Configurati
|
||||
/** DEPRECATED: Alias for UntrustedDataToUnknownExternalApiConfig */
|
||||
deprecated class UntrustedDataToUnknownExternalAPIConfig = UntrustedDataToUnknownExternalApiConfig;
|
||||
|
||||
private module UntrustedDataToUnknownExternalApiConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof UntrustedFlowSource }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof UnknownExternalApiDataNode }
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks data flow from `RemoteFlowSource`s to `UnknownExternalApiDataNode`s.
|
||||
*/
|
||||
module UntrustedDataToUnknownExternalApiFlow =
|
||||
DataFlow::Global<UntrustedDataToUnknownExternalApiConfig>;
|
||||
|
||||
/** A node representing untrusted data being passed to an external API. */
|
||||
class UntrustedExternalApiDataNode extends ExternalApiDataNode {
|
||||
UntrustedExternalApiDataNode() { any(UntrustedDataToExternalApiConfig c).hasFlow(_, this) }
|
||||
UntrustedExternalApiDataNode() { UntrustedDataToExternalApiFlow::flow(_, this) }
|
||||
|
||||
/** Gets a source of untrusted data which is passed to this external API data node. */
|
||||
DataFlow::Node getAnUntrustedSource() {
|
||||
any(UntrustedDataToExternalApiConfig c).hasFlow(result, this)
|
||||
}
|
||||
DataFlow::Node getAnUntrustedSource() { UntrustedDataToExternalApiFlow::flow(result, this) }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for UntrustedExternalApiDataNode */
|
||||
|
||||
@@ -51,11 +51,13 @@ private predicate isIncorrectIntegerConversion(int sourceBitSize, int sinkBitSiz
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED: use `Flow` instead.
|
||||
*
|
||||
* A taint-tracking configuration for reasoning about when an integer
|
||||
* obtained from parsing a string flows to a type conversion to a smaller
|
||||
* integer types, which could cause unexpected values.
|
||||
*/
|
||||
class ConversionWithoutBoundsCheckConfig extends TaintTracking::Configuration {
|
||||
deprecated class ConversionWithoutBoundsCheckConfig extends TaintTracking::Configuration {
|
||||
boolean sinkIsSigned;
|
||||
int sourceBitSize;
|
||||
int sinkBitSize;
|
||||
@@ -148,6 +150,119 @@ class ConversionWithoutBoundsCheckConfig extends TaintTracking::Configuration {
|
||||
}
|
||||
}
|
||||
|
||||
/** Flow state for ConversionWithoutBoundsCheckConfig. */
|
||||
newtype IntegerConversionFlowState =
|
||||
/** Keep track of info about the source and potential sinks. */
|
||||
TFlowstate(boolean sinkIsSigned, int sourceBitSize, int sinkBitSize) {
|
||||
sinkIsSigned in [true, false] and
|
||||
isIncorrectIntegerConversion(sourceBitSize, sinkBitSize)
|
||||
}
|
||||
|
||||
/** Gets the bit size of the source. */
|
||||
int getSourceBitSize(IntegerConversionFlowState state) { state = TFlowstate(_, result, _) }
|
||||
|
||||
private module ConversionWithoutBoundsCheckConfig implements DataFlow::StateConfigSig {
|
||||
class FlowState = IntegerConversionFlowState;
|
||||
|
||||
predicate isSource(DataFlow::Node source, FlowState state) {
|
||||
exists(
|
||||
DataFlow::CallNode c, IntegerParser::Range ip, int apparentBitSize, int effectiveBitSize
|
||||
|
|
||||
c.getTarget() = ip and source = c.getResult(0)
|
||||
|
|
||||
(
|
||||
apparentBitSize = ip.getTargetBitSize()
|
||||
or
|
||||
// If we are reading a variable, check if it is
|
||||
// `strconv.IntSize`, and use 0 if it is.
|
||||
exists(DataFlow::Node rawBitSize | rawBitSize = ip.getTargetBitSizeInput().getNode(c) |
|
||||
if rawBitSize = any(Strconv::IntSize intSize).getARead()
|
||||
then apparentBitSize = 0
|
||||
else apparentBitSize = rawBitSize.getIntValue()
|
||||
)
|
||||
) and
|
||||
(
|
||||
if apparentBitSize = 0
|
||||
then effectiveBitSize = getIntTypeBitSize(source.getFile())
|
||||
else effectiveBitSize = apparentBitSize
|
||||
) and
|
||||
// `effectiveBitSize` could be any value between 0 and 64, but we
|
||||
// can round it up to the nearest size of an integer type without
|
||||
// changing behavior.
|
||||
exists(int sourceBitSize |
|
||||
sourceBitSize = min(int b | b in [0, 8, 16, 32, 64] and b >= effectiveBitSize)
|
||||
|
|
||||
state = TFlowstate(_, sourceBitSize, _)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `sink` is a typecast to an integer type with size `bitSize` (where
|
||||
* 0 represents architecture-dependent) and the expression being typecast is
|
||||
* not also in a right-shift expression. We allow this case because it is
|
||||
* a common pattern to serialise `byte(v)`, `byte(v >> 8)`, and so on.
|
||||
*/
|
||||
additional predicate isSinkWithBitSize(
|
||||
DataFlow::TypeCastNode sink, boolean sinkIsSigned, int bitSize
|
||||
) {
|
||||
sink.asExpr() instanceof ConversionExpr and
|
||||
exists(IntegerType integerType | sink.getResultType().getUnderlyingType() = integerType |
|
||||
(
|
||||
bitSize = integerType.getSize()
|
||||
or
|
||||
not exists(integerType.getSize()) and
|
||||
bitSize = getIntTypeBitSize(sink.getFile())
|
||||
) and
|
||||
if integerType instanceof SignedIntegerType then sinkIsSigned = true else sinkIsSigned = false
|
||||
) and
|
||||
not exists(ShrExpr shrExpr |
|
||||
shrExpr.getLeftOperand().getGlobalValueNumber() =
|
||||
sink.getOperand().asExpr().getGlobalValueNumber() or
|
||||
shrExpr.getLeftOperand().(AndExpr).getAnOperand().getGlobalValueNumber() =
|
||||
sink.getOperand().asExpr().getGlobalValueNumber()
|
||||
)
|
||||
}
|
||||
|
||||
predicate isSink(DataFlow::Node sink, FlowState state) {
|
||||
// We use the argument of the type conversion as the configuration sink so that we
|
||||
// can sanitize the result of the conversion to prevent flow on to further sinks
|
||||
// without needing to use `isSanitizerOut`, which doesn't work with flow states
|
||||
// (and therefore the legacy `TaintTracking::Configuration` class).
|
||||
exists(boolean sinkIsSigned, int sinkBitSize |
|
||||
state = TFlowstate(sinkIsSigned, _, sinkBitSize)
|
||||
|
|
||||
isSinkWithBitSize(sink.getASuccessor(), sinkIsSigned, sinkBitSize)
|
||||
)
|
||||
}
|
||||
|
||||
predicate isBarrier(DataFlow::Node node, FlowState state) {
|
||||
exists(boolean sinkIsSigned, int sourceBitSize, int sinkBitSize |
|
||||
state = TFlowstate(sinkIsSigned, sourceBitSize, sinkBitSize)
|
||||
|
|
||||
// To catch flows that only happen on 32-bit architectures we
|
||||
// consider an architecture-dependent sink bit size to be 32.
|
||||
exists(UpperBoundCheckGuard g, int bitSize |
|
||||
if sinkBitSize != 0 then bitSize = sinkBitSize else bitSize = 32
|
||||
|
|
||||
node = DataFlow::BarrierGuard<upperBoundCheckGuard/3>::getABarrierNodeForGuard(g) and
|
||||
g.isBoundFor(bitSize, sinkIsSigned)
|
||||
)
|
||||
or
|
||||
exists(int bitSize |
|
||||
isIncorrectIntegerConversion(sourceBitSize, bitSize) and
|
||||
isSinkWithBitSize(node, sinkIsSigned, bitSize)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks taint flow from an integer obtained from parsing a string that flows
|
||||
* to a type conversion to a smaller integer type, which could cause data loss.
|
||||
*/
|
||||
module Flow = TaintTracking::GlobalWithState<ConversionWithoutBoundsCheckConfig>;
|
||||
|
||||
private predicate upperBoundCheckGuard(DataFlow::Node g, Expr e, boolean branch) {
|
||||
g.(UpperBoundCheckGuard).checks(e, branch)
|
||||
}
|
||||
|
||||
@@ -17,10 +17,12 @@ module InsecureRandomness {
|
||||
import InsecureRandomnessCustomizations::InsecureRandomness
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `Flow` instead.
|
||||
*
|
||||
* A taint-tracking configuration for reasoning about random values that are
|
||||
* not cryptographically secure.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "InsecureRandomness" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -32,4 +34,21 @@ module InsecureRandomness {
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
/** Holds if `sink` is a sink for this configuration with kind `kind`. */
|
||||
predicate isSinkWithKind(Sink sink, string kind) { kind = sink.getKind() }
|
||||
|
||||
private module Config implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { isSinkWithKind(sink, _) }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks taint flow from randomly generated values which are not
|
||||
* cryptographically secure to cryptographic applications.
|
||||
*/
|
||||
module Flow = TaintTracking::Global<Config>;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ module LogInjection {
|
||||
import LogInjectionCustomizations::LogInjection
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `Flow` instead.
|
||||
*
|
||||
* A taint-tracking configuration for reasoning about log injection vulnerabilities.
|
||||
*/
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
@@ -31,9 +33,7 @@ module LogInjection {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for reasoning about log injection vulnerabilities.
|
||||
*/
|
||||
/** Config for reasoning about log injection vulnerabilities. */
|
||||
module Config implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
@@ -42,5 +42,6 @@ module LogInjection {
|
||||
predicate isBarrier(DataFlow::Node sanitizer) { sanitizer instanceof Sanitizer }
|
||||
}
|
||||
|
||||
/** Tracks taint flow for reasoning about log injection vulnerabilities. */
|
||||
module Flow = TaintTracking::Global<Config>;
|
||||
}
|
||||
|
||||
@@ -18,9 +18,11 @@ module OpenUrlRedirect {
|
||||
import OpenUrlRedirectCustomizations::OpenUrlRedirect
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `Flow` instead.
|
||||
*
|
||||
* A data-flow configuration for reasoning about unvalidated URL redirections.
|
||||
*/
|
||||
class Configuration extends DataFlow::Configuration {
|
||||
deprecated class Configuration extends DataFlow::Configuration {
|
||||
Configuration() { this = "OpenUrlRedirect" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -63,4 +65,45 @@ module OpenUrlRedirect {
|
||||
guard instanceof BarrierGuard
|
||||
}
|
||||
}
|
||||
|
||||
private module Config implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Barrier }
|
||||
|
||||
predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
// taint steps that do not include flow through fields
|
||||
TaintTracking::localTaintStep(pred, succ) and not TaintTracking::fieldReadStep(pred, succ)
|
||||
or
|
||||
// explicit extra taint steps for this query
|
||||
any(AdditionalStep s).hasTaintStep(pred, succ)
|
||||
or
|
||||
// propagate to a URL when its host is assigned to
|
||||
exists(Write w, Field f, SsaWithFields v | f.hasQualifiedName("net/url", "URL", "Host") |
|
||||
w.writesField(v.getAUse(), f, pred) and succ = v.getAUse()
|
||||
)
|
||||
or
|
||||
// propagate out of most URL fields, but not `ForceQuery` and `Scheme`
|
||||
exists(Field f, string fn |
|
||||
f.hasQualifiedName("net/url", "URL", fn) and
|
||||
not fn in ["ForceQuery", "Scheme"]
|
||||
|
|
||||
succ.(Read).readsField(pred, f)
|
||||
)
|
||||
}
|
||||
|
||||
predicate isBarrierOut(DataFlow::Node node) {
|
||||
// block propagation of this unsafe value when its host is overwritten
|
||||
exists(Write w, Field f | f.hasQualifiedName("net/url", "URL", "Host") |
|
||||
w.writesField(node.getASuccessor(), f, _)
|
||||
)
|
||||
or
|
||||
hostnameSanitizingPrefixEdge(node, _)
|
||||
}
|
||||
}
|
||||
|
||||
/** Tracks taint flow from unvalidated, untrusted data to URL redirections. */
|
||||
module Flow = DataFlow::Global<Config>;
|
||||
}
|
||||
|
||||
@@ -17,9 +17,11 @@ module ReflectedXss {
|
||||
import ReflectedXssCustomizations::ReflectedXss
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `Flow` instead.
|
||||
*
|
||||
* A taint-tracking configuration for reasoning about XSS.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "ReflectedXss" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -35,4 +37,15 @@ module ReflectedXss {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
private module Config implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
/** Tracks taint flow from untrusted data to XSS attack vectors. */
|
||||
module Flow = TaintTracking::Global<Config>;
|
||||
}
|
||||
|
||||
@@ -17,9 +17,11 @@ module RequestForgery {
|
||||
import RequestForgeryCustomizations::RequestForgery
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `Flow` instead.
|
||||
*
|
||||
* A taint-tracking configuration for reasoning about request forgery.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "RequestForgery" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -47,4 +49,24 @@ module RequestForgery {
|
||||
super.isSanitizerGuard(guard) or guard instanceof SanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
private module Config implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
predicate isBarrierOut(DataFlow::Node node) { node instanceof SanitizerEdge }
|
||||
|
||||
predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
// propagate to a URL when its host is assigned to
|
||||
exists(Write w, Field f, SsaWithFields v | f.hasQualifiedName("net/url", "URL", "Host") |
|
||||
w.writesField(v.getAUse(), f, pred) and succ = v.getAUse()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Tracks taint flow from untrusted data to request forgery attack vectors. */
|
||||
module Flow = TaintTracking::Global<Config>;
|
||||
}
|
||||
|
||||
@@ -17,9 +17,11 @@ module SafeUrlFlow {
|
||||
import SafeUrlFlowCustomizations::SafeUrlFlow
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `Flow` instead.
|
||||
*
|
||||
* A taint-tracking configuration for reasoning about safe URLs.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "SafeUrlFlow" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -42,4 +44,29 @@ module SafeUrlFlow {
|
||||
node instanceof SanitizerEdge
|
||||
}
|
||||
}
|
||||
|
||||
private module Config implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
// propagate to a URL when its host is assigned to
|
||||
exists(Write w, Field f, SsaWithFields v | f.hasQualifiedName("net/url", "URL", "Host") |
|
||||
w.writesField(v.getAUse(), f, node1) and node2 = v.getAUse()
|
||||
)
|
||||
}
|
||||
|
||||
predicate isBarrierOut(DataFlow::Node node) {
|
||||
// block propagation of this safe value when its host is overwritten
|
||||
exists(Write w, Field f | f.hasQualifiedName("net/url", "URL", "Host") |
|
||||
w.writesField(node.getASuccessor(), f, _)
|
||||
)
|
||||
or
|
||||
node instanceof SanitizerEdge
|
||||
}
|
||||
}
|
||||
|
||||
/** Tracks taint flow for reasoning about safe URLs. */
|
||||
module Flow = TaintTracking::Global<Config>;
|
||||
}
|
||||
|
||||
@@ -14,9 +14,11 @@ module SqlInjection {
|
||||
import SqlInjectionCustomizations::SqlInjection
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `Flow` instead.
|
||||
*
|
||||
* A taint-tracking configuration for reasoning about SQL-injection vulnerabilities.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "SqlInjection" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -36,4 +38,19 @@ module SqlInjection {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
private module Config implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
NoSql::isAdditionalMongoTaintStep(pred, succ)
|
||||
}
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
/** Tracks taint flow for reasoning about SQL-injection vulnerabilities. */
|
||||
module Flow = TaintTracking::Global<Config>;
|
||||
}
|
||||
|
||||
@@ -17,9 +17,11 @@ import CommandInjectionCustomizations
|
||||
*/
|
||||
module StoredCommand {
|
||||
/**
|
||||
* DEPRECATED: Use `Flow` instead.
|
||||
*
|
||||
* A taint-tracking configuration for reasoning about command-injection vulnerabilities.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "StoredCommand" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
@@ -39,4 +41,19 @@ module StoredCommand {
|
||||
guard instanceof CommandInjection::SanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
private module Config implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) {
|
||||
source instanceof StoredXss::Source and
|
||||
// exclude file names, since those are not generally an issue
|
||||
not source instanceof StoredXss::FileNameSource
|
||||
}
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof CommandInjection::Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof CommandInjection::Sanitizer }
|
||||
}
|
||||
|
||||
/** Tracks taint flow for reasoning about command-injection vulnerabilities. */
|
||||
module Flow = TaintTracking::Global<Config>;
|
||||
}
|
||||
|
||||
@@ -17,9 +17,11 @@ module StoredXss {
|
||||
import StoredXssCustomizations::StoredXss
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `Flow` instead.
|
||||
*
|
||||
* A taint-tracking configuration for reasoning about XSS.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
deprecated class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "StoredXss" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
@@ -35,4 +37,15 @@ module StoredXss {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
private module Config implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
|
||||
/** Tracks taint flow for reasoning about XSS. */
|
||||
module Flow = TaintTracking::Global<Config>;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user