mirror of
https://github.com/github/codeql.git
synced 2025-12-25 05:06:34 +01:00
203 lines
5.7 KiB
Plaintext
203 lines
5.7 KiB
Plaintext
/**
|
|
* @name Missing return-value check for a 'scanf'-like function
|
|
* @description Failing to check that a call to 'scanf' actually writes to an
|
|
* output variable can lead to unexpected behavior at reading time.
|
|
* @kind problem
|
|
* @problem.severity warning
|
|
* @security-severity 7.5
|
|
* @precision medium
|
|
* @id cpp/missing-check-scanf
|
|
* @tags security
|
|
* correctness
|
|
* external/cwe/cwe-252
|
|
* external/cwe/cwe-253
|
|
*/
|
|
|
|
import cpp
|
|
import semmle.code.cpp.commons.Scanf
|
|
import semmle.code.cpp.controlflow.Guards
|
|
import semmle.code.cpp.dataflow.new.DataFlow::DataFlow
|
|
import semmle.code.cpp.ir.IR
|
|
import semmle.code.cpp.ir.ValueNumbering
|
|
|
|
/** Holds if `n` reaches an argument to a call to a `scanf`-like function. */
|
|
pragma[nomagic]
|
|
predicate revFlow0(Node n) {
|
|
isSink(_, _, n, _)
|
|
or
|
|
exists(Node succ | revFlow0(succ) | localFlowStep(n, succ))
|
|
}
|
|
|
|
/**
|
|
* Holds if `n` represents an uninitialized stack-allocated variable, or a
|
|
* newly (and presumed uninitialized) heap allocation.
|
|
*/
|
|
predicate isUninitialized(Node n) {
|
|
exists(n.asUninitialized()) or
|
|
n.asIndirectExpr(1) instanceof AllocationExpr
|
|
}
|
|
|
|
pragma[nomagic]
|
|
predicate fwdFlow0(Node n) {
|
|
revFlow0(n) and
|
|
(
|
|
isUninitialized(n)
|
|
or
|
|
exists(Node prev |
|
|
fwdFlow0(prev) and
|
|
localFlowStep(prev, n)
|
|
)
|
|
)
|
|
}
|
|
|
|
predicate isSink(ScanfFunctionCall call, int index, Node n, Expr input) {
|
|
input = call.getOutputArgument(index) and
|
|
n.asIndirectExpr() = input
|
|
}
|
|
|
|
/**
|
|
* Holds if `call` is a `scanf`-like call and `output` is the `index`'th
|
|
* argument that has not been previously initialized.
|
|
*/
|
|
predicate isRelevantScanfCall(ScanfFunctionCall call, int index, Expr output) {
|
|
exists(Node n | fwdFlow0(n) and isSink(call, index, n, output))
|
|
}
|
|
|
|
/**
|
|
* Holds if `call` is a `scanf`-like function that may write to `output` at
|
|
* index `index` and `n` is the dataflow node that represents the data after
|
|
* it has been written to by `call`.
|
|
*/
|
|
predicate isSource(ScanfFunctionCall call, int index, Node n, Expr output) {
|
|
isRelevantScanfCall(call, index, output) and
|
|
output = call.getOutputArgument(index) and
|
|
n.asDefiningArgument() = output
|
|
}
|
|
|
|
/**
|
|
* Holds if `n` is reachable from an output argument of a relevant call to
|
|
* a `scanf`-like function.
|
|
*/
|
|
pragma[nomagic]
|
|
predicate fwdFlow(Node n) {
|
|
isSource(_, _, n, _)
|
|
or
|
|
exists(Node prev |
|
|
fwdFlow(prev) and
|
|
localFlowStep(prev, n) and
|
|
not isSanitizerOut(prev)
|
|
)
|
|
}
|
|
|
|
/** Holds if `n` should not have outgoing flow. */
|
|
predicate isSanitizerOut(Node n) {
|
|
// We disable flow out of sinks to reduce result duplication
|
|
isSink(n, _)
|
|
or
|
|
// If the node is being passed to a function it may be
|
|
// modified, and thus it's safe to later read the value.
|
|
exists(n.asIndirectArgument())
|
|
}
|
|
|
|
/**
|
|
* Holds if `n` is a node such that `n.asExpr() = e` and `e` is not an
|
|
* argument of a deallocation expression.
|
|
*/
|
|
predicate isSink(Node n, Expr e) {
|
|
n.asExpr() = e and
|
|
not any(DeallocationExpr dealloc).getFreedExpr() = e
|
|
}
|
|
|
|
/**
|
|
* Holds if `n` is part of a path from a call to a `scanf`-like function
|
|
* to a use of the written variable.
|
|
*/
|
|
pragma[nomagic]
|
|
predicate revFlow(Node n) {
|
|
fwdFlow(n) and
|
|
(
|
|
isSink(n, _)
|
|
or
|
|
exists(Node succ |
|
|
revFlow(succ) and
|
|
localFlowStep(n, succ) and
|
|
not isSanitizerOut(n)
|
|
)
|
|
)
|
|
}
|
|
|
|
/** A local flow step, restricted to relevant dataflow nodes. */
|
|
private predicate step(Node n1, Node n2) {
|
|
revFlow(n1) and
|
|
revFlow(n2) and
|
|
localFlowStep(n1, n2)
|
|
}
|
|
|
|
predicate hasFlow(Node n1, Node n2) = fastTC(step/2)(n1, n2)
|
|
|
|
/**
|
|
* Holds if `source` is the `index`'th argument to the `scanf`-like call `call`, and `sink` is
|
|
* a dataflow node that represents the expression `e`.
|
|
*/
|
|
predicate hasFlow(Node source, ScanfFunctionCall call, int index, Node sink, Expr e) {
|
|
isSource(call, index, source, _) and
|
|
hasFlow(source, sink) and
|
|
isSink(sink, e)
|
|
}
|
|
|
|
/**
|
|
* Gets the smallest possible `scanf` return value of `call` that would indicate
|
|
* success in writing the output argument at index `index`.
|
|
*/
|
|
int getMinimumGuardConstant(ScanfFunctionCall call, int index) {
|
|
isSource(call, index, _, _) and
|
|
result =
|
|
index + 1 -
|
|
count(ScanfFormatLiteral f, int n |
|
|
// Special case: %n writes to an argument without reading any input.
|
|
// It does not increase the count returned by `scanf`.
|
|
n <= index and f.getUse() = call and f.getConversionChar(n) = "n"
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds the access to `e` isn't guarded by a check that ensures that `call` returned
|
|
* at least `minGuard`.
|
|
*/
|
|
predicate hasNonGuardedAccess(ScanfFunctionCall call, Expr e, int minGuard) {
|
|
exists(int index |
|
|
hasFlow(_, call, index, _, e) and
|
|
minGuard = getMinimumGuardConstant(call, index)
|
|
|
|
|
not exists(int value |
|
|
e.getBasicBlock() = blockGuardedBy(value, "==", call) and minGuard <= value
|
|
or
|
|
e.getBasicBlock() = blockGuardedBy(value, "<", call) and minGuard - 1 <= value
|
|
or
|
|
e.getBasicBlock() = blockGuardedBy(value, "<=", call) and minGuard <= value
|
|
)
|
|
)
|
|
}
|
|
|
|
/** Returns a block guarded by the assertion of `value op call` */
|
|
BasicBlock blockGuardedBy(int value, string op, ScanfFunctionCall call) {
|
|
exists(GuardCondition g, Expr left, Expr right |
|
|
right = g.getAChild() and
|
|
value = left.getValue().toInt() and
|
|
localExprFlow(call, right)
|
|
|
|
|
g.ensuresEq(left, right, 0, result, true) and op = "=="
|
|
or
|
|
g.ensuresLt(left, right, 0, result, true) and op = "<"
|
|
or
|
|
g.ensuresLt(left, right, 1, result, true) and op = "<="
|
|
)
|
|
}
|
|
|
|
from ScanfFunctionCall call, Expr e, int minGuard
|
|
where hasNonGuardedAccess(call, e, minGuard)
|
|
select e,
|
|
"This variable is read, but may not have been written. " +
|
|
"It should be guarded by a check that the $@ returns at least " + minGuard + ".", call,
|
|
call.toString()
|