Files
codeql/cpp/ql/src/Likely Bugs/Format/NonConstantFormat.ql
2023-01-10 13:36:01 +01:00

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."