Merge branch 'master' into rdmarsh/cpp/ir-flow-through-outparams

This commit is contained in:
Robert Marsh
2020-04-08 12:32:35 -07:00
301 changed files with 7949 additions and 2957 deletions

View File

@@ -4,7 +4,7 @@ private predicate freed(Expr e) {
e = any(DeallocationExpr de).getFreedExpr()
or
exists(ExprCall c |
// cautiously assume that any ExprCall could be a freeCall.
// cautiously assume that any `ExprCall` could be a deallocation expression.
c.getAnArgument() = e
)
}

View File

@@ -5,17 +5,34 @@
import cpp
import semmle.code.cpp.controlflow.SSA
import semmle.code.cpp.dataflow.DataFlow
import semmle.code.cpp.models.implementations.Allocation
import semmle.code.cpp.models.implementations.Deallocation
/**
* Holds if `alloc` is a use of `malloc` or `new`. `kind` is
* a string describing the type of the allocation.
*/
predicate allocExpr(Expr alloc, string kind) {
isAllocationExpr(alloc) and
not alloc.isFromUninstantiatedTemplate(_) and
(
alloc instanceof FunctionCall and
kind = "malloc"
exists(Function target |
alloc.(AllocationExpr).(FunctionCall).getTarget() = target and
(
target.getName() = "operator new" and
kind = "new" and
// exclude placement new and custom overloads as they
// may not conform to assumptions
not target.getNumberOfParameters() > 1
or
target.getName() = "operator new[]" and
kind = "new[]" and
// exclude placement new and custom overloads as they
// may not conform to assumptions
not target.getNumberOfParameters() > 1
or
not target instanceof OperatorNewAllocationFunction and
kind = "malloc"
)
)
or
alloc instanceof NewExpr and
kind = "new" and
@@ -28,7 +45,8 @@ predicate allocExpr(Expr alloc, string kind) {
// exclude placement new and custom overloads as they
// may not conform to assumptions
not alloc.(NewArrayExpr).getAllocatorCall().getTarget().getNumberOfParameters() > 1
)
) and
not alloc.isFromUninstantiatedTemplate(_)
}
/**
@@ -110,8 +128,20 @@ predicate allocReaches(Expr e, Expr alloc, string kind) {
* describing the type of that free or delete.
*/
predicate freeExpr(Expr free, Expr freed, string kind) {
freeCall(free, freed) and
kind = "free"
exists(Function target |
freed = free.(DeallocationExpr).getFreedExpr() and
free.(FunctionCall).getTarget() = target and
(
target.getName() = "operator delete" and
kind = "delete"
or
target.getName() = "operator delete[]" and
kind = "delete[]"
or
not target instanceof OperatorDeleteDeallocationFunction and
kind = "free"
)
)
or
free.(DeleteExpr).getExpr() = freed and
kind = "delete"

View File

@@ -8,6 +8,6 @@ struct S {
// Whereas here it does make a semantic difference.
auto getValCorrect() const -> int {
return val
return val;
}
};

View File

@@ -2,7 +2,7 @@
* @name Uncontrolled data used in path expression
* @description Accessing paths influenced by users can allow an
* attacker to access unexpected resources.
* @kind problem
* @kind path-problem
* @problem.severity warning
* @precision medium
* @id cpp/path-injection
@@ -17,6 +17,7 @@ import cpp
import semmle.code.cpp.security.FunctionWithWrappers
import semmle.code.cpp.security.Security
import semmle.code.cpp.security.TaintTracking
import TaintedWithPath
/**
* A function for opening a file.
@@ -51,12 +52,19 @@ class FileFunction extends FunctionWithWrappers {
override predicate interestingArg(int arg) { arg = 0 }
}
class TaintedPathConfiguration extends TaintTrackingConfiguration {
override predicate isSink(Element tainted) {
exists(FileFunction fileFunction | fileFunction.outermostWrapperFunctionCall(tainted, _))
}
}
from
FileFunction fileFunction, Expr taintedArg, Expr taintSource, string taintCause, string callChain
FileFunction fileFunction, Expr taintedArg, Expr taintSource, PathNode sourceNode,
PathNode sinkNode, string taintCause, string callChain
where
fileFunction.outermostWrapperFunctionCall(taintedArg, callChain) and
tainted(taintSource, taintedArg) and
taintedWithPath(taintSource, taintedArg, sourceNode, sinkNode) and
isUserInput(taintSource, taintCause)
select taintedArg,
select taintedArg, sourceNode, sinkNode,
"This argument to a file access function is derived from $@ and then passed to " + callChain,
taintSource, "user input (" + taintCause + ")"

View File

@@ -2,7 +2,7 @@
* @name CGI script vulnerable to cross-site scripting
* @description Writing user input directly to a web page
* allows for a cross-site scripting vulnerability.
* @kind problem
* @kind path-problem
* @problem.severity error
* @precision high
* @id cpp/cgi-xss
@@ -13,6 +13,7 @@
import cpp
import semmle.code.cpp.commons.Environment
import semmle.code.cpp.security.TaintTracking
import TaintedWithPath
/** A call that prints its arguments to `stdout`. */
class PrintStdoutCall extends FunctionCall {
@@ -27,8 +28,13 @@ class QueryString extends EnvironmentRead {
QueryString() { getEnvironmentVariable() = "QUERY_STRING" }
}
from QueryString query, PrintStdoutCall call, Element printedArg
where
call.getAnArgument() = printedArg and
tainted(query, printedArg)
select printedArg, "Cross-site scripting vulnerability due to $@.", query, "this query data"
class Configuration extends TaintTrackingConfiguration {
override predicate isSink(Element tainted) {
exists(PrintStdoutCall call | call.getAnArgument() = tainted)
}
}
from QueryString query, Element printedArg, PathNode sourceNode, PathNode sinkNode
where taintedWithPath(query, printedArg, sourceNode, sinkNode)
select printedArg, sourceNode, sinkNode, "Cross-site scripting vulnerability due to $@.", query,
"this query data"

View File

@@ -3,7 +3,7 @@
* @description Including user-supplied data in a SQL query without
* neutralizing special elements can make code vulnerable
* to SQL Injection.
* @kind problem
* @kind path-problem
* @problem.severity error
* @precision high
* @id cpp/sql-injection
@@ -15,6 +15,7 @@ import cpp
import semmle.code.cpp.security.Security
import semmle.code.cpp.security.FunctionWithWrappers
import semmle.code.cpp.security.TaintTracking
import TaintedWithPath
class SQLLikeFunction extends FunctionWithWrappers {
SQLLikeFunction() { sqlArgument(this.getName(), _) }
@@ -22,11 +23,19 @@ class SQLLikeFunction extends FunctionWithWrappers {
override predicate interestingArg(int arg) { sqlArgument(this.getName(), arg) }
}
from SQLLikeFunction runSql, Expr taintedArg, Expr taintSource, string taintCause, string callChain
class Configuration extends TaintTrackingConfiguration {
override predicate isSink(Element tainted) {
exists(SQLLikeFunction runSql | runSql.outermostWrapperFunctionCall(tainted, _))
}
}
from
SQLLikeFunction runSql, Expr taintedArg, Expr taintSource, PathNode sourceNode, PathNode sinkNode,
string taintCause, string callChain
where
runSql.outermostWrapperFunctionCall(taintedArg, callChain) and
tainted(taintSource, taintedArg) and
taintedWithPath(taintSource, taintedArg, sourceNode, sinkNode) and
isUserInput(taintSource, taintCause)
select taintedArg,
select taintedArg, sourceNode, sinkNode,
"This argument to a SQL query function is derived from $@ and then passed to " + callChain,
taintSource, "user input (" + taintCause + ")"

View File

@@ -3,7 +3,7 @@
* @description Using externally controlled strings in a process
* operation can allow an attacker to execute malicious
* commands.
* @kind problem
* @kind path-problem
* @problem.severity warning
* @precision medium
* @id cpp/uncontrolled-process-operation
@@ -14,13 +14,24 @@
import cpp
import semmle.code.cpp.security.Security
import semmle.code.cpp.security.TaintTracking
import TaintedWithPath
from string processOperation, int processOperationArg, FunctionCall call, Expr arg, Element source
predicate isProcessOperationExplanation(Expr arg, string processOperation) {
exists(int processOperationArg, FunctionCall call |
isProcessOperationArgument(processOperation, processOperationArg) and
call.getTarget().getName() = processOperation and
call.getArgument(processOperationArg) = arg
)
}
class Configuration extends TaintTrackingConfiguration {
override predicate isSink(Element arg) { isProcessOperationExplanation(arg, _) }
}
from string processOperation, Expr arg, Expr source, PathNode sourceNode, PathNode sinkNode
where
isProcessOperationArgument(processOperation, processOperationArg) and
call.getTarget().getName() = processOperation and
call.getArgument(processOperationArg) = arg and
tainted(source, arg)
select arg,
isProcessOperationExplanation(arg, processOperation) and
taintedWithPath(source, arg, sourceNode, sinkNode)
select arg, sourceNode, sinkNode,
"The value of this argument may come from $@ and is being passed to " + processOperation, source,
source.toString()

View File

@@ -2,7 +2,7 @@
* @name Unbounded write
* @description Buffer write operations that do not control the length
* of data written may overflow.
* @kind problem
* @kind path-problem
* @problem.severity error
* @precision medium
* @id cpp/unbounded-write
@@ -16,6 +16,7 @@
import semmle.code.cpp.security.BufferWrite
import semmle.code.cpp.security.Security
import semmle.code.cpp.security.TaintTracking
import TaintedWithPath
/*
* --- Summary of CWE-120 alerts ---
@@ -54,32 +55,48 @@ predicate isUnboundedWrite(BufferWrite bw) {
* }
*/
/**
* Holds if `e` is a source buffer going into an unbounded write `bw` or a
* qualifier of (a qualifier of ...) such a source.
*/
predicate unboundedWriteSource(Expr e, BufferWrite bw) {
isUnboundedWrite(bw) and e = bw.getASource()
or
exists(FieldAccess fa | unboundedWriteSource(fa, bw) and e = fa.getQualifier())
}
/*
* --- user input reach ---
*/
/**
* Identifies expressions that are potentially tainted with user
* input. Most of the work for this is actually done by the
* TaintTracking library.
*/
predicate tainted2(Expr expr, Expr inputSource, string inputCause) {
taintedIncludingGlobalVars(inputSource, expr, _) and
inputCause = inputSource.toString()
or
exists(Expr e | tainted2(e, inputSource, inputCause) |
// field accesses of a tainted struct are tainted
e = expr.(FieldAccess).getQualifier()
)
class Configuration extends TaintTrackingConfiguration {
override predicate isSink(Element tainted) { unboundedWriteSource(tainted, _) }
override predicate taintThroughGlobals() { any() }
}
/*
* --- put it together ---
*/
from BufferWrite bw, Expr inputSource, string inputCause
/*
* An unbounded write is, for example `strcpy(..., tainted)`. We're looking
* for a tainted source buffer of an unbounded write, where this source buffer
* is a sink in the taint-tracking analysis.
*
* In the case of `gets` and `scanf`, where the source buffer is implicit, the
* `BufferWrite` library reports the source buffer to be the same as the
* destination buffer. Since those destination-buffer arguments are also
* modeled in the taint-tracking library as being _sources_ of taint, they are
* in practice reported as being tainted because the `security.TaintTracking`
* library does not distinguish between taint going into an argument and out of
* an argument. Thus, we get the desired alerts.
*/
from BufferWrite bw, Expr inputSource, Expr tainted, PathNode sourceNode, PathNode sinkNode
where
isUnboundedWrite(bw) and
tainted2(bw.getASource(), inputSource, inputCause)
select bw, "This '" + bw.getBWDesc() + "' with input from $@ may overflow the destination.",
inputSource, inputCause
taintedWithPath(inputSource, tainted, sourceNode, sinkNode) and
unboundedWriteSource(tainted, bw)
select bw, sourceNode, sinkNode,
"This '" + bw.getBWDesc() + "' with input from $@ may overflow the destination.", inputSource,
inputSource.toString()

View File

@@ -3,7 +3,7 @@
* @description Using externally-controlled format strings in
* printf-style functions can lead to buffer overflows
* or data representation problems.
* @kind problem
* @kind path-problem
* @problem.severity warning
* @precision medium
* @id cpp/tainted-format-string
@@ -16,12 +16,21 @@ import cpp
import semmle.code.cpp.security.Security
import semmle.code.cpp.security.FunctionWithWrappers
import semmle.code.cpp.security.TaintTracking
import TaintedWithPath
from PrintfLikeFunction printf, Expr arg, string printfFunction, Expr userValue, string cause
class Configuration extends TaintTrackingConfiguration {
override predicate isSink(Element tainted) {
exists(PrintfLikeFunction printf | printf.outermostWrapperFunctionCall(tainted, _))
}
}
from
PrintfLikeFunction printf, Expr arg, PathNode sourceNode, PathNode sinkNode,
string printfFunction, Expr userValue, string cause
where
printf.outermostWrapperFunctionCall(arg, printfFunction) and
tainted(userValue, arg) and
taintedWithPath(userValue, arg, sourceNode, sinkNode) and
isUserInput(userValue, cause)
select arg,
select arg, sourceNode, sinkNode,
"The value of this argument may come from $@ and is being used as a formatting argument to " +
printfFunction, userValue, cause

View File

@@ -3,7 +3,7 @@
* @description Using externally-controlled format strings in
* printf-style functions can lead to buffer overflows
* or data representation problems.
* @kind problem
* @kind path-problem
* @problem.severity warning
* @precision medium
* @id cpp/tainted-format-string-through-global
@@ -16,15 +16,24 @@ import cpp
import semmle.code.cpp.security.FunctionWithWrappers
import semmle.code.cpp.security.Security
import semmle.code.cpp.security.TaintTracking
import TaintedWithPath
class Configuration extends TaintTrackingConfiguration {
override predicate isSink(Element tainted) {
exists(PrintfLikeFunction printf | printf.outermostWrapperFunctionCall(tainted, _))
}
override predicate taintThroughGlobals() { any() }
}
from
PrintfLikeFunction printf, Expr arg, string printfFunction, Expr userValue, string cause,
string globalVar
PrintfLikeFunction printf, Expr arg, PathNode sourceNode, PathNode sinkNode,
string printfFunction, Expr userValue, string cause
where
printf.outermostWrapperFunctionCall(arg, printfFunction) and
not tainted(_, arg) and
taintedIncludingGlobalVars(userValue, arg, globalVar) and
not taintedWithoutGlobals(arg) and
taintedWithPath(userValue, arg, sourceNode, sinkNode) and
isUserInput(userValue, cause)
select arg,
"This value may flow through $@, originating from $@, and is a formatting argument to " +
printfFunction + ".", globalVarFromId(globalVar), globalVar, userValue, cause
select arg, sourceNode, sinkNode,
"The value of this argument may come from $@ and is being used as a formatting argument to " +
printfFunction, userValue, cause

View File

@@ -2,7 +2,7 @@
* @name Uncontrolled data in arithmetic expression
* @description Arithmetic operations on uncontrolled data that is not
* validated can cause overflows.
* @kind problem
* @kind path-problem
* @problem.severity warning
* @precision medium
* @id cpp/uncontrolled-arithmetic
@@ -15,6 +15,7 @@ import cpp
import semmle.code.cpp.security.Overflow
import semmle.code.cpp.security.Security
import semmle.code.cpp.security.TaintTracking
import TaintedWithPath
predicate isRandCall(FunctionCall fc) { fc.getTarget().getName() = "rand" }
@@ -40,9 +41,22 @@ class SecurityOptionsArith extends SecurityOptions {
}
}
predicate taintedVarAccess(Expr origin, VariableAccess va) {
isUserInput(origin, _) and
tainted(origin, va)
predicate isDiv(VariableAccess va) { exists(AssignDivExpr div | div.getLValue() = va) }
predicate missingGuard(VariableAccess va, string effect) {
exists(Operation op | op.getAnOperand() = va |
missingGuardAgainstUnderflow(op, va) and effect = "underflow"
or
missingGuardAgainstOverflow(op, va) and effect = "overflow"
)
}
class Configuration extends TaintTrackingConfiguration {
override predicate isSink(Element e) {
isDiv(e)
or
missingGuard(e, _)
}
}
/**
@@ -50,19 +64,17 @@ predicate taintedVarAccess(Expr origin, VariableAccess va) {
* range.
*/
predicate guardedByAssignDiv(Expr origin) {
isUserInput(origin, _) and
exists(AssignDivExpr div, VariableAccess va | tainted(origin, va) and div.getLValue() = va)
exists(VariableAccess va |
taintedWithPath(origin, va, _, _) and
isDiv(va)
)
}
from Expr origin, Operation op, VariableAccess va, string effect
from Expr origin, VariableAccess va, string effect, PathNode sourceNode, PathNode sinkNode
where
taintedVarAccess(origin, va) and
op.getAnOperand() = va and
(
missingGuardAgainstUnderflow(op, va) and effect = "underflow"
or
missingGuardAgainstOverflow(op, va) and effect = "overflow"
) and
taintedWithPath(origin, va, sourceNode, sinkNode) and
missingGuard(va, effect) and
not guardedByAssignDiv(origin)
select va, "$@ flows to here and is used in arithmetic, potentially causing an " + effect + ".",
origin, "Uncontrolled value"
select va, sourceNode, sinkNode,
"$@ flows to here and is used in arithmetic, potentially causing an " + effect + ".", origin,
"Uncontrolled value"

View File

@@ -2,7 +2,7 @@
* @name Overflow in uncontrolled allocation size
* @description Allocating memory with a size controlled by an external
* user can result in integer overflow.
* @kind problem
* @kind path-problem
* @problem.severity error
* @precision high
* @id cpp/uncontrolled-allocation-size
@@ -13,21 +13,33 @@
import cpp
import semmle.code.cpp.security.TaintTracking
import TaintedWithPath
predicate taintedAllocSize(Expr e, Expr source, string taintCause) {
predicate taintedChild(Expr e, Expr tainted) {
(
isAllocationExpr(e) or
isAllocationExpr(e)
or
any(MulExpr me | me.getAChild() instanceof SizeofOperator) = e
) and
tainted = e.getAChild() and
tainted.getUnspecifiedType() instanceof IntegralType
}
class TaintedAllocationSizeConfiguration extends TaintTrackingConfiguration {
override predicate isSink(Element tainted) { taintedChild(_, tainted) }
}
predicate taintedAllocSize(
Expr e, Expr source, PathNode sourceNode, PathNode sinkNode, string taintCause
) {
isUserInput(source, taintCause) and
exists(Expr tainted |
tainted = e.getAChild() and
tainted.getUnspecifiedType() instanceof IntegralType and
isUserInput(source, taintCause) and
tainted(source, tainted)
taintedChild(e, tainted) and
taintedWithPath(source, tainted, sourceNode, sinkNode)
)
}
from Expr e, Expr source, string taintCause
where taintedAllocSize(e, source, taintCause)
select e, "This allocation size is derived from $@ and might overflow", source,
"user input (" + taintCause + ")"
from Expr e, Expr source, PathNode sourceNode, PathNode sinkNode, string taintCause
where taintedAllocSize(e, source, sourceNode, sinkNode, taintCause)
select e, sourceNode, sinkNode, "This allocation size is derived from $@ and might overflow",
source, "user input (" + taintCause + ")"

View File

@@ -3,7 +3,7 @@
* @description Authentication by checking that the peer's address
* matches a known IP or web address is unsafe as it is
* vulnerable to spoofing attacks.
* @kind problem
* @kind path-problem
* @problem.severity warning
* @precision medium
* @id cpp/user-controlled-bypass
@@ -12,6 +12,7 @@
*/
import semmle.code.cpp.security.TaintTracking
import TaintedWithPath
predicate hardCodedAddressOrIP(StringLiteral txt) {
exists(string s | s = txt.getValueText() |
@@ -102,16 +103,21 @@ predicate useOfHardCodedAddressOrIP(Expr use) {
* untrusted input then it might be vulnerable to a spoofing
* attack.
*/
predicate hardCodedAddressInCondition(Expr source, Expr condition) {
// One of the sub-expressions of the condition is tainted.
exists(Expr taintedExpr | taintedExpr.getParent+() = condition | tainted(source, taintedExpr)) and
predicate hardCodedAddressInCondition(Expr subexpression, Expr condition) {
subexpression = condition.getAChild+() and
// One of the sub-expressions of the condition is a hard-coded
// IP or web-address.
exists(Expr use | use.getParent+() = condition | useOfHardCodedAddressOrIP(use)) and
exists(Expr use | use = condition.getAChild+() | useOfHardCodedAddressOrIP(use)) and
condition = any(IfStmt ifStmt).getCondition()
}
from Expr source, Expr condition
where hardCodedAddressInCondition(source, condition)
select condition, "Untrusted input $@ might be vulnerable to a spoofing attack.", source,
source.toString()
class Configuration extends TaintTrackingConfiguration {
override predicate isSink(Element sink) { hardCodedAddressInCondition(sink, _) }
}
from Expr subexpression, Expr source, Expr condition, PathNode sourceNode, PathNode sinkNode
where
hardCodedAddressInCondition(subexpression, condition) and
taintedWithPath(source, subexpression, sourceNode, sinkNode)
select condition, sourceNode, sinkNode,
"Untrusted input $@ might be vulnerable to a spoofing attack.", source, source.toString()

View File

@@ -2,7 +2,7 @@
* @name Cleartext storage of sensitive information in buffer
* @description Storing sensitive information in cleartext can expose it
* to an attacker.
* @kind problem
* @kind path-problem
* @problem.severity warning
* @precision medium
* @id cpp/cleartext-storage-buffer
@@ -14,12 +14,20 @@ import cpp
import semmle.code.cpp.security.BufferWrite
import semmle.code.cpp.security.TaintTracking
import semmle.code.cpp.security.SensitiveExprs
import TaintedWithPath
from BufferWrite w, Expr taintedArg, Expr taintSource, string taintCause, SensitiveExpr dest
class Configuration extends TaintTrackingConfiguration {
override predicate isSink(Element tainted) { exists(BufferWrite w | w.getASource() = tainted) }
}
from
BufferWrite w, Expr taintedArg, Expr taintSource, PathNode sourceNode, PathNode sinkNode,
string taintCause, SensitiveExpr dest
where
tainted(taintSource, taintedArg) and
taintedWithPath(taintSource, taintedArg, sourceNode, sinkNode) and
isUserInput(taintSource, taintCause) and
w.getASource() = taintedArg and
dest = w.getDest()
select w, "This write into buffer '" + dest.toString() + "' may contain unencrypted data from $@",
select w, sourceNode, sinkNode,
"This write into buffer '" + dest.toString() + "' may contain unencrypted data from $@",
taintSource, "user input (" + taintCause + ")"

View File

@@ -2,7 +2,7 @@
* @name Cleartext storage of sensitive information in an SQLite database
* @description Storing sensitive information in a non-encrypted
* database can expose it to an attacker.
* @kind problem
* @kind path-problem
* @problem.severity warning
* @precision medium
* @id cpp/cleartext-storage-database
@@ -13,6 +13,7 @@
import cpp
import semmle.code.cpp.security.SensitiveExprs
import semmle.code.cpp.security.TaintTracking
import TaintedWithPath
class UserInputIsSensitiveExpr extends SecurityOptions {
override predicate isUserInput(Expr expr, string cause) {
@@ -32,10 +33,21 @@ predicate sqlite_encryption_used() {
any(FunctionCall fc).getTarget().getName().matches("sqlite%\\_key\\_%")
}
from SensitiveExpr taintSource, Expr taintedArg, SqliteFunctionCall sqliteCall
class Configuration extends TaintTrackingConfiguration {
override predicate isSink(Element taintedArg) {
exists(SqliteFunctionCall sqliteCall |
taintedArg = sqliteCall.getASource() and
not sqlite_encryption_used()
)
}
}
from
SensitiveExpr taintSource, Expr taintedArg, SqliteFunctionCall sqliteCall, PathNode sourceNode,
PathNode sinkNode
where
tainted(taintSource, taintedArg) and
taintedArg = sqliteCall.getASource() and
not sqlite_encryption_used()
select sqliteCall, "This SQLite call may store $@ in a non-encrypted SQLite database", taintSource,
taintedWithPath(taintSource, taintedArg, sourceNode, sinkNode) and
taintedArg = sqliteCall.getASource()
select sqliteCall, sourceNode, sinkNode,
"This SQLite call may store $@ in a non-encrypted SQLite database", taintSource,
"sensitive information"

View File

@@ -3,7 +3,7 @@
* @description Using untrusted inputs in a statement that makes a
* security decision makes code vulnerable to
* attack.
* @kind problem
* @kind path-problem
* @problem.severity warning
* @precision medium
* @id cpp/tainted-permissions-check
@@ -12,14 +12,9 @@
*/
import semmle.code.cpp.security.TaintTracking
import TaintedWithPath
/**
* Holds if there is an 'if' statement whose condition `condition`
* is influenced by tainted data `source`, and the body contains
* `raise` which escalates privilege.
*/
predicate cwe807violation(Expr source, Expr condition, Expr raise) {
tainted(source, condition) and
predicate sensitiveCondition(Expr condition, Expr raise) {
raisesPrivilege(raise) and
exists(IfStmt ifstmt |
ifstmt.getCondition() = condition and
@@ -27,7 +22,19 @@ predicate cwe807violation(Expr source, Expr condition, Expr raise) {
)
}
from Expr source, Expr condition, Expr raise
where cwe807violation(source, condition, raise)
select condition, "Reliance on untrusted input $@ to raise privilege at $@", source,
source.toString(), raise, raise.toString()
class Configuration extends TaintTrackingConfiguration {
override predicate isSink(Element tainted) { sensitiveCondition(tainted, _) }
}
/*
* Produce an alert if there is an 'if' statement whose condition `condition`
* is influenced by tainted data `source`, and the body contains
* `raise` which escalates privilege.
*/
from Expr source, Expr condition, Expr raise, PathNode sourceNode, PathNode sinkNode
where
taintedWithPath(source, condition, sourceNode, sinkNode) and
sensitiveCondition(condition, raise)
select condition, sourceNode, sinkNode, "Reliance on untrusted input $@ to raise privilege at $@",
source, source.toString(), raise, raise.toString()

View File

@@ -0,0 +1,4 @@
- description: Standard Code Scanning queries for C and C++
- qlpack: codeql-cpp
- apply: code-scanning-selectors.yml
from: codeql-suite-helpers

View File

@@ -23,6 +23,8 @@ predicate freeFunction(Function f, int argNum) { argNum = f.(DeallocationFunctio
/**
* A call to a library routine that frees memory.
*
* DEPRECATED: Use `DeallocationExpr` instead (this also includes `delete` expressions).
*/
predicate freeCall(FunctionCall fc, Expr arg) { arg = fc.(DeallocationExpr).getFreedExpr() }

View File

@@ -2,6 +2,7 @@ import cpp
import semmle.code.cpp.security.Security
private import semmle.code.cpp.ir.dataflow.DataFlow
private import semmle.code.cpp.ir.dataflow.DataFlow2
private import semmle.code.cpp.ir.dataflow.DataFlow3
private import semmle.code.cpp.ir.IR
private import semmle.code.cpp.ir.dataflow.internal.DataFlowDispatch as Dispatch
private import semmle.code.cpp.models.interfaces.Taint
@@ -143,7 +144,17 @@ private predicate writesVariable(StoreInstruction store, Variable var) {
}
/**
* A variable that has any kind of upper-bound check anywhere in the program
* A variable that has any kind of upper-bound check anywhere in the program. This is
* biased towards being inclusive because there are a lot of valid ways of doing an
* upper bounds checks if we don't consider where it occurs, for example:
* ```
* if (x < 10) { sink(x); }
*
* if (10 > y) { sink(y); }
*
* if (z > 10) { z = 10; }
* sink(z);
* ```
*/
// TODO: This coarse overapproximation, ported from the old taint tracking
// library, could be replaced with an actual semantic check that a particular
@@ -152,10 +163,10 @@ private predicate writesVariable(StoreInstruction store, Variable var) {
// previously suppressed by this predicate by coincidence.
private predicate hasUpperBoundsCheck(Variable var) {
exists(RelationalOperation oper, VariableAccess access |
oper.getLeftOperand() = access and
oper.getAnOperand() = access and
access.getTarget() = var and
// Comparing to 0 is not an upper bound check
not oper.getRightOperand().getValue() = "0"
not oper.getAnOperand().getValue() = "0"
)
}
@@ -171,6 +182,7 @@ private predicate nodeIsBarrierIn(DataFlow::Node node) {
node = getNodeForSource(any(Expr e))
}
cached
private predicate instructionTaintStep(Instruction i1, Instruction i2) {
// Expressions computed from tainted data are also tainted
exists(CallInstruction call, int argIndex | call = i2 |
@@ -191,9 +203,22 @@ private predicate instructionTaintStep(Instruction i1, Instruction i2) {
or
i2.(UnaryInstruction).getUnary() = i1
or
i2.(ChiInstruction).getPartial() = i1 and
// Flow out of definition-by-reference
i2.(ChiInstruction).getPartial() = i1.(WriteSideEffectInstruction) and
not i2.isResultConflated()
or
// Flow from an element to an array or union that contains it.
i2.(ChiInstruction).getPartial() = i1 and
not i2.isResultConflated() and
exists(Type t | i2.getResultLanguageType().hasType(t, false) |
t instanceof Union
or
t instanceof ArrayType
or
// Buffers or unknown size
t instanceof UnknownType
)
or
exists(BinaryInstruction bin |
bin = i2 and
predictableInstruction(i2.getAnOperand().getDef()) and
@@ -350,6 +375,16 @@ private Element adjustedSink(DataFlow::Node sink) {
result.(AssignOperation).getAnOperand() = sink.asExpr()
}
/**
* Holds if `tainted` may contain taint from `source`.
*
* A tainted expression is either directly user input, or is
* computed from user input in a way that users can probably
* control the exact output of the computation.
*
* This doesn't include data flow through global variables.
* If you need that you must call `taintedIncludingGlobalVars`.
*/
cached
predicate tainted(Expr source, Element tainted) {
exists(DefaultTaintTrackingCfg cfg, DataFlow::Node sink |
@@ -358,16 +393,21 @@ predicate tainted(Expr source, Element tainted) {
)
}
predicate tainted_instruction(
Function sourceFunc, Instruction source, Function sinkFunc, Instruction sink
) {
sourceFunc = source.getEnclosingFunction() and
sinkFunc = sink.getEnclosingFunction() and
exists(DefaultTaintTrackingCfg cfg |
cfg.hasFlow(DataFlow::instructionNode(source), DataFlow::instructionNode(sink))
)
}
/**
* Holds if `tainted` may contain taint from `source`, where the taint passed
* through a global variable named `globalVar`.
*
* A tainted expression is either directly user input, or is
* computed from user input in a way that users can probably
* control the exact output of the computation.
*
* This version gives the same results as tainted but also includes
* data flow through global variables.
*
* The parameter `globalVar` is the qualified name of the last global variable
* used to move the value from source to tainted. If the taint did not pass
* through a global variable, then `globalVar = ""`.
*/
cached
predicate taintedIncludingGlobalVars(Expr source, Element tainted, string globalVar) {
tainted(source, tainted) and
@@ -385,11 +425,245 @@ predicate taintedIncludingGlobalVars(Expr source, Element tainted, string global
)
}
/**
* Gets the global variable whose qualified name is `id`. Use this predicate
* together with `taintedIncludingGlobalVars`. Example:
*
* ```
* exists(string varName |
* taintedIncludingGlobalVars(source, tainted, varName) and
* var = globalVarFromId(varName)
* )
* ```
*/
GlobalOrNamespaceVariable globalVarFromId(string id) { id = result.getQualifiedName() }
/**
* Resolve potential target function(s) for `call`.
*
* If `call` is a call through a function pointer (`ExprCall`) or
* targets a virtual method, simple data flow analysis is performed
* in order to identify target(s).
*/
Function resolveCall(Call call) {
exists(CallInstruction callInstruction |
callInstruction.getAST() = call and
result = Dispatch::viableCallable(callInstruction)
)
}
/**
* Provides definitions for augmenting source/sink pairs with data-flow paths
* between them. From a `@kind path-problem` query, import this module in the
* global scope, extend `TaintTrackingConfiguration`, and use `taintedWithPath`
* in place of `tainted`.
*
* Importing this module will also import the query predicates that contain the
* taint paths.
*/
module TaintedWithPath {
private newtype TSingleton = MkSingleton()
/**
* A taint-tracking configuration that matches sources and sinks in the same
* way as the `tainted` predicate.
*
* Override `isSink` and `taintThroughGlobals` as needed, but do not provide
* a characteristic predicate.
*/
class TaintTrackingConfiguration extends TSingleton {
/** Override this to specify which elements are sinks in this configuration. */
abstract predicate isSink(Element e);
/**
* Override this predicate to `any()` to allow taint to flow through global
* variables.
*/
predicate taintThroughGlobals() { none() }
/** Gets a textual representation of this element. */
string toString() { result = "TaintTrackingConfiguration" }
}
private class AdjustedConfiguration extends DataFlow3::Configuration {
AdjustedConfiguration() { this = "AdjustedConfiguration" }
override predicate isSource(DataFlow::Node source) { source = getNodeForSource(_) }
override predicate isSink(DataFlow::Node sink) {
exists(TaintTrackingConfiguration cfg | cfg.isSink(adjustedSink(sink)))
}
override predicate isAdditionalFlowStep(DataFlow::Node n1, DataFlow::Node n2) {
instructionTaintStep(n1.asInstruction(), n2.asInstruction())
or
exists(TaintTrackingConfiguration cfg | cfg.taintThroughGlobals() |
writesVariable(n1.asInstruction(), n2.asVariable().(GlobalOrNamespaceVariable))
or
readsVariable(n2.asInstruction(), n1.asVariable().(GlobalOrNamespaceVariable))
)
}
override predicate isBarrier(DataFlow::Node node) { nodeIsBarrier(node) }
override predicate isBarrierIn(DataFlow::Node node) { nodeIsBarrierIn(node) }
}
/*
* A sink `Element` may map to multiple `DataFlowX::PathNode`s via (the
* inverse of) `adjustedSink`. For example, an `Expr` maps to all its
* conversions, and a `Variable` maps to all loads and stores from it. Because
* the path node is part of the tuple that constitutes the alert, this leads
* to duplicate alerts.
*
* To avoid showing duplicates, we edit the graph to replace the final node
* coming from the data-flow library with a node that matches exactly the
* `Element` sink that's requested.
*
* The same is done for sources.
*/
private newtype TPathNode =
TWrapPathNode(DataFlow3::PathNode n) or
// There's a single newtype constructor for both sources and sinks since
// that makes it easiest to deal with the case where source = sink.
TEndpointPathNode(Element e) {
exists(AdjustedConfiguration cfg, DataFlow3::Node sourceNode, DataFlow3::Node sinkNode |
cfg.hasFlow(sourceNode, sinkNode)
|
sourceNode = getNodeForSource(e)
or
e = adjustedSink(sinkNode) and
exists(TaintTrackingConfiguration ttCfg | ttCfg.isSink(e))
)
}
/** An opaque type used for the nodes of a data-flow path. */
class PathNode extends TPathNode {
/** Gets a textual representation of this element. */
string toString() { none() }
/**
* 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://help.semmle.com/QL/learn-ql/ql/locations.html).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
none()
}
}
private class WrapPathNode extends PathNode, TWrapPathNode {
DataFlow3::PathNode inner() { this = TWrapPathNode(result) }
override string toString() { result = this.inner().toString() }
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
this.inner().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
}
private class EndpointPathNode extends PathNode, TEndpointPathNode {
Expr inner() { this = TEndpointPathNode(result) }
override string toString() { result = this.inner().toString() }
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
this
.inner()
.getLocation()
.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
}
/** A PathNode whose `Element` is a source. It may also be a sink. */
private class InitialPathNode extends EndpointPathNode {
InitialPathNode() { exists(getNodeForSource(this.inner())) }
}
/** A PathNode whose `Element` is a sink. It may also be a source. */
private class FinalPathNode extends EndpointPathNode {
FinalPathNode() { exists(TaintTrackingConfiguration cfg | cfg.isSink(this.inner())) }
}
/** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */
query predicate edges(PathNode a, PathNode b) {
DataFlow3::PathGraph::edges(a.(WrapPathNode).inner(), b.(WrapPathNode).inner())
or
// To avoid showing trivial-looking steps, we _replace_ the last node instead
// of adding an edge out of it.
exists(WrapPathNode sinkNode |
DataFlow3::PathGraph::edges(a.(WrapPathNode).inner(), sinkNode.inner()) and
b.(FinalPathNode).inner() = adjustedSink(sinkNode.inner().getNode())
)
or
// Same for the first node
exists(WrapPathNode sourceNode |
DataFlow3::PathGraph::edges(sourceNode.inner(), b.(WrapPathNode).inner()) and
sourceNode.inner().getNode() = getNodeForSource(a.(InitialPathNode).inner())
)
or
// Finally, handle the case where the path goes directly from a source to a
// sink, meaning that they both need to be translated.
exists(WrapPathNode sinkNode, WrapPathNode sourceNode |
DataFlow3::PathGraph::edges(sourceNode.inner(), sinkNode.inner()) and
sourceNode.inner().getNode() = getNodeForSource(a.(InitialPathNode).inner()) and
b.(FinalPathNode).inner() = adjustedSink(sinkNode.inner().getNode())
)
}
/** Holds if `n` is a node in the graph of data flow path explanations. */
query predicate nodes(PathNode n, string key, string val) {
key = "semmle.label" and val = n.toString()
}
/**
* Holds if `tainted` may contain taint from `source`, where `sourceNode` and
* `sinkNode` are the corresponding `PathNode`s that can be used in a query
* to provide path explanations. Extend `TaintTrackingConfiguration` to use
* this predicate.
*
* A tainted expression is either directly user input, or is computed from
* user input in a way that users can probably control the exact output of
* the computation.
*/
predicate taintedWithPath(Expr source, Element tainted, PathNode sourceNode, PathNode sinkNode) {
exists(AdjustedConfiguration cfg, DataFlow3::Node flowSource, DataFlow3::Node flowSink |
source = sourceNode.(InitialPathNode).inner() and
flowSource = getNodeForSource(source) and
cfg.hasFlow(flowSource, flowSink) and
tainted = adjustedSink(flowSink) and
tainted = sinkNode.(FinalPathNode).inner()
)
}
private predicate isGlobalVariablePathNode(WrapPathNode n) {
n.inner().getNode().asVariable() instanceof GlobalOrNamespaceVariable
}
private predicate edgesWithoutGlobals(PathNode a, PathNode b) {
edges(a, b) and
not isGlobalVariablePathNode(a) and
not isGlobalVariablePathNode(b)
}
/**
* Holds if `tainted` can be reached from a taint source without passing
* through a global variable.
*/
predicate taintedWithoutGlobals(Element tainted) {
exists(PathNode sourceNode, FinalPathNode sinkNode |
sourceNode.(WrapPathNode).inner().getNode() = getNodeForSource(_) and
edgesWithoutGlobals+(sourceNode, sinkNode) and
tainted = sinkNode.inner()
)
}
}

View File

@@ -209,7 +209,7 @@ Type getErasedRepr(Type t) {
}
/** Gets a string representation of a type returned by `getErasedRepr`. */
string ppReprType(Type t) { result = t.toString() }
string ppReprType(Type t) { none() } // stub implementation
/**
* Holds if `t1` and `t2` are compatible, that is, whether data can flow from

View File

@@ -239,6 +239,17 @@ class DefinitionByReferenceNode extends InstructionNode {
Parameter getParameter() {
exists(CallInstruction ci | result = ci.getStaticCallTarget().getParameter(instr.getIndex()))
}
override string toString() {
// This string should be unique enough to be helpful but common enough to
// avoid storing too many different strings.
result =
instr.getPrimaryInstruction().(CallInstruction).getStaticCallTarget().getName() +
" output argument"
or
not exists(instr.getPrimaryInstruction().(CallInstruction).getStaticCallTarget()) and
result = "output argument"
}
}
/**
@@ -289,7 +300,7 @@ ExprNode exprNode(Expr e) { result.getExpr() = e }
* Gets the `Node` corresponding to `e`, if any. Here, `e` may be a
* `Conversion`.
*/
ExprNode convertedExprNode(Expr e) { result.getExpr() = e }
ExprNode convertedExprNode(Expr e) { result.getConvertedExpr() = e }
/**
* Gets the `Node` corresponding to the value of `p` at function entry.
@@ -323,6 +334,7 @@ predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
simpleInstructionLocalFlowStep(nodeFrom.asInstruction(), nodeTo.asInstruction())
}
cached
private predicate simpleInstructionLocalFlowStep(Instruction iFrom, Instruction iTo) {
iTo.(CopyInstruction).getSourceValue() = iFrom
or

View File

@@ -0,0 +1,17 @@
private import internal.ValueNumberingImports
private import ValueNumbering
/**
* Provides additional information about value numbering in IR dumps.
*/
class ValueNumberPropertyProvider extends IRPropertyProvider {
override string getInstructionProperty(Instruction instr, string key) {
exists(ValueNumber vn |
vn = valueNumber(instr) and
key = "valnum" and
if strictcount(vn.getAnInstruction()) > 1
then result = vn.getDebugString()
else result = "unique"
)
}
}

View File

@@ -1,21 +1,6 @@
private import internal.ValueNumberingInternal
private import internal.ValueNumberingImports
/**
* Provides additional information about value numbering in IR dumps.
*/
class ValueNumberPropertyProvider extends IRPropertyProvider {
override string getInstructionProperty(Instruction instr, string key) {
exists(ValueNumber vn |
vn = valueNumber(instr) and
key = "valnum" and
if strictcount(vn.getAnInstruction()) > 1
then result = vn.getDebugString()
else result = "unique"
)
}
}
/**
* The value number assigned to a particular set of instructions that produce equivalent results.
*/

View File

@@ -0,0 +1,17 @@
private import internal.ValueNumberingImports
private import ValueNumbering
/**
* Provides additional information about value numbering in IR dumps.
*/
class ValueNumberPropertyProvider extends IRPropertyProvider {
override string getInstructionProperty(Instruction instr, string key) {
exists(ValueNumber vn |
vn = valueNumber(instr) and
key = "valnum" and
if strictcount(vn.getAnInstruction()) > 1
then result = vn.getDebugString()
else result = "unique"
)
}
}

View File

@@ -1,21 +1,6 @@
private import internal.ValueNumberingInternal
private import internal.ValueNumberingImports
/**
* Provides additional information about value numbering in IR dumps.
*/
class ValueNumberPropertyProvider extends IRPropertyProvider {
override string getInstructionProperty(Instruction instr, string key) {
exists(ValueNumber vn |
vn = valueNumber(instr) and
key = "valnum" and
if strictcount(vn.getAnInstruction()) > 1
then result = vn.getDebugString()
else result = "unique"
)
}
}
/**
* The value number assigned to a particular set of instructions that produce equivalent results.
*/

View File

@@ -0,0 +1,17 @@
private import internal.ValueNumberingImports
private import ValueNumbering
/**
* Provides additional information about value numbering in IR dumps.
*/
class ValueNumberPropertyProvider extends IRPropertyProvider {
override string getInstructionProperty(Instruction instr, string key) {
exists(ValueNumber vn |
vn = valueNumber(instr) and
key = "valnum" and
if strictcount(vn.getAnInstruction()) > 1
then result = vn.getDebugString()
else result = "unique"
)
}
}

View File

@@ -1,21 +1,6 @@
private import internal.ValueNumberingInternal
private import internal.ValueNumberingImports
/**
* Provides additional information about value numbering in IR dumps.
*/
class ValueNumberPropertyProvider extends IRPropertyProvider {
override string getInstructionProperty(Instruction instr, string key) {
exists(ValueNumber vn |
vn = valueNumber(instr) and
key = "valnum" and
if strictcount(vn.getAnInstruction()) > 1
then result = vn.getDebugString()
else result = "unique"
)
}
}
/**
* The value number assigned to a particular set of instructions that produce equivalent results.
*/

View File

@@ -12,4 +12,5 @@ private import implementations.Strcat
private import implementations.Strcpy
private import implementations.Strdup
private import implementations.Strftime
private import implementations.StdString
private import implementations.Swap

View File

@@ -1,3 +1,9 @@
/**
* Provides implementation classes modelling various methods of allocation
* (`malloc`, `new` etc). See `semmle.code.cpp.models.interfaces.Allocation`
* for usage information.
*/
import semmle.code.cpp.models.interfaces.Allocation
/**

View File

@@ -1,4 +1,10 @@
import semmle.code.cpp.models.interfaces.Allocation
/**
* Provides implementation classes modelling various methods of deallocation
* (`free`, `delete` etc). See `semmle.code.cpp.models.interfaces.Deallocation`
* for usage information.
*/
import semmle.code.cpp.models.interfaces.Deallocation
/**
* A deallocation function such as `free`.

View File

@@ -0,0 +1,27 @@
import semmle.code.cpp.models.interfaces.Taint
/**
* The `std::basic_string` constructor(s).
*/
class StdStringConstructor extends TaintFunction {
StdStringConstructor() { this.hasQualifiedName("std", "basic_string", "basic_string") }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// flow from any constructor argument to return value
input.isParameter(_) and
output.isReturnValue()
}
}
/**
* The standard function `std::string.c_str`.
*/
class StdStringCStr extends TaintFunction {
StdStringCStr() { this.hasQualifiedName("std", "basic_string", "c_str") }
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
// flow from string itself (qualifier) to return value
input.isQualifierObject() and
output.isReturnValue()
}
}

View File

@@ -328,14 +328,24 @@ GlobalOrNamespaceVariable globalVarFromId(string id) {
}
/**
* A variable that has any kind of upper-bound check anywhere in the program
* A variable that has any kind of upper-bound check anywhere in the program. This is
* biased towards being inclusive because there are a lot of valid ways of doing an
* upper bounds checks if we don't consider where it occurs, for example:
* ```
* if (x < 10) { sink(x); }
*
* if (10 > y) { sink(y); }
*
* if (z > 10) { z = 10; }
* sink(z);
* ```
*/
private predicate hasUpperBoundsCheck(Variable var) {
exists(RelationalOperation oper, VariableAccess access |
oper.getLeftOperand() = access and
oper.getAnOperand() = access and
access.getTarget() = var and
// Comparing to 0 is not an upper bound check
not oper.getRightOperand().getValue() = "0"
not oper.getAnOperand().getValue() = "0"
)
}