mirror of
https://github.com/github/codeql.git
synced 2026-07-20 02:38:23 +02:00
144 lines
4.0 KiB
Plaintext
144 lines
4.0 KiB
Plaintext
/**
|
|
* @name Non-constant format string
|
|
* @description Passing a non-constant 'format' string to a printf-like function can lead
|
|
* to a mismatch between the number of arguments defined by the 'format' and the number
|
|
* of arguments actually passed to the function. If the format string ultimately stems
|
|
* from an untrusted source, this can be used for exploits.
|
|
* @kind problem
|
|
* @problem.severity recommendation
|
|
* @security-severity 9.3
|
|
* @precision high
|
|
* @id cpp/non-constant-format
|
|
* @tags maintainability
|
|
* correctness
|
|
* security
|
|
* external/cwe/cwe-134
|
|
*/
|
|
|
|
import semmle.code.cpp.dataflow.TaintTracking
|
|
import semmle.code.cpp.commons.Printf
|
|
|
|
// For the following `...gettext` functions, we assume that
|
|
// all translations preserve the type and order of `%` specifiers
|
|
// (and hence are safe to use as format strings). This
|
|
// assumption is hard-coded into the query.
|
|
predicate whitelistFunction(Function f, int arg) {
|
|
// basic variations of gettext
|
|
f.getName() = "_" and arg = 0
|
|
or
|
|
f.getName() = "gettext" and arg = 0
|
|
or
|
|
f.getName() = "dgettext" and arg = 1
|
|
or
|
|
f.getName() = "dcgettext" and arg = 1
|
|
or
|
|
// plural variations of gettext that take one format string for singular and another for plural form
|
|
f.getName() = "ngettext" and
|
|
(arg = 0 or arg = 1)
|
|
or
|
|
f.getName() = "dngettext" and
|
|
(arg = 1 or arg = 2)
|
|
or
|
|
f.getName() = "dcngettext" and
|
|
(arg = 1 or arg = 2)
|
|
}
|
|
|
|
// we assume that ALL uses of the `_` macro
|
|
// return constant string literals
|
|
predicate underscoreMacro(Expr e) {
|
|
exists(MacroInvocation mi |
|
|
mi.getMacroName() = "_" and
|
|
mi.getExpr() = e
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `t` cannot hold a character array, directly or indirectly.
|
|
*/
|
|
predicate cannotContainString(Type t) {
|
|
t.getUnspecifiedType() instanceof BuiltInType
|
|
or
|
|
t.getUnspecifiedType() instanceof IntegralOrEnumType
|
|
}
|
|
|
|
predicate isNonConst(DataFlow::Node node) {
|
|
exists(Expr e | e = node.asExpr() |
|
|
exists(FunctionCall fc | fc = e |
|
|
not (
|
|
whitelistFunction(fc.getTarget(), _) or
|
|
fc.getTarget().hasDefinition()
|
|
)
|
|
)
|
|
or
|
|
exists(Parameter p | p = e.(VariableAccess).getTarget() |
|
|
p.getFunction().getName() = "main" and p.getType() instanceof PointerType
|
|
)
|
|
or
|
|
e instanceof CrementOperation
|
|
or
|
|
e instanceof AddressOfExpr
|
|
or
|
|
e instanceof ReferenceToExpr
|
|
or
|
|
e instanceof AssignPointerAddExpr
|
|
or
|
|
e instanceof AssignPointerSubExpr
|
|
or
|
|
e instanceof PointerArithmeticOperation
|
|
or
|
|
e instanceof FieldAccess
|
|
or
|
|
e instanceof PointerDereferenceExpr
|
|
or
|
|
e instanceof AddressOfExpr
|
|
or
|
|
e instanceof ExprCall
|
|
or
|
|
e instanceof NewArrayExpr
|
|
or
|
|
e instanceof AssignExpr
|
|
or
|
|
exists(Variable v | v = e.(VariableAccess).getTarget() |
|
|
v.getType().(ArrayType).getBaseType() instanceof CharType and
|
|
exists(AssignExpr ae |
|
|
ae.getLValue().(ArrayExpr).getArrayBase().(VariableAccess).getTarget() = v
|
|
)
|
|
)
|
|
)
|
|
or
|
|
node instanceof DataFlow::DefinitionByReferenceNode
|
|
}
|
|
|
|
pragma[noinline]
|
|
predicate isSanitizerNode(DataFlow::Node node) {
|
|
underscoreMacro(node.asExpr())
|
|
or
|
|
cannotContainString(node.getType())
|
|
}
|
|
|
|
class NonConstFlow extends TaintTracking::Configuration {
|
|
NonConstFlow() { this = "NonConstFlow" }
|
|
|
|
override predicate isSource(DataFlow::Node source) {
|
|
isNonConst(source) and
|
|
not cannotContainString(source.getType())
|
|
}
|
|
|
|
override predicate isSink(DataFlow::Node sink) {
|
|
exists(FormattingFunctionCall fc | sink.asExpr() = fc.getArgument(fc.getFormatParameterIndex()))
|
|
}
|
|
|
|
override predicate isSanitizer(DataFlow::Node node) { isSanitizerNode(node) }
|
|
}
|
|
|
|
from FormattingFunctionCall call, Expr formatString
|
|
where
|
|
call.getArgument(call.getFormatParameterIndex()) = formatString and
|
|
exists(NonConstFlow cf, DataFlow::Node sink |
|
|
cf.hasFlowTo(sink) and
|
|
sink.asExpr() = formatString
|
|
)
|
|
select formatString,
|
|
"The format string argument to " + call.getTarget().getName() +
|
|
" should be constant to prevent security issues and other potential errors."
|