mirror of
https://github.com/github/codeql.git
synced 2026-04-30 11:15:13 +02:00
Merge pull request #1251 from zlaski-semmle/zlaski/cpp370
[CPP-370] Non-constant `format` arguments to `printf` and friends
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>The <code>printf</code> function, related functions like <code>sprintf</code> and <code>fprintf</code>,
|
||||
and other functions built atop <code>vprintf</code> all accept a format string as their first argument.
|
||||
and other functions built atop <code>vprintf</code> all accept a format string as one of their arguments.
|
||||
When such format strings are literal constants, it is easy for the programmer (and static analysis tools)
|
||||
to verify that the format specifiers (such as <code>%s</code> and <code>%02x</code>) in the format string
|
||||
are compatible with the trailing arguments of the function call. When such format strings are not literal
|
||||
|
||||
@@ -13,48 +13,37 @@
|
||||
* security
|
||||
* external/cwe/cwe-134
|
||||
*/
|
||||
import cpp
|
||||
|
||||
/**
|
||||
* Holds if `t` is a character array or pointer, where `charType` is the type of
|
||||
* characters in `t`.
|
||||
*/
|
||||
predicate stringType(Type t, Type charType) {
|
||||
(
|
||||
( charType = t.(PointerType).getBaseType() or
|
||||
charType = t.(ArrayType).getBaseType()
|
||||
) and (
|
||||
charType.getUnspecifiedType() instanceof CharType or
|
||||
charType.getUnspecifiedType() instanceof Wchar_t
|
||||
)
|
||||
)
|
||||
or
|
||||
stringType(t.getUnderlyingType(), charType)
|
||||
or
|
||||
stringType(t.(SpecifiedType).getBaseType(), charType)
|
||||
}
|
||||
import semmle.code.cpp.dataflow.TaintTracking
|
||||
import semmle.code.cpp.commons.Printf
|
||||
|
||||
predicate gettextFunction(Function f, int arg) {
|
||||
// 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
|
||||
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))
|
||||
}
|
||||
|
||||
predicate stringArray(Variable arr, AggregateLiteral init) {
|
||||
arr.getInitializer().getExpr() = init and
|
||||
stringType(arr.getUnspecifiedType().(ArrayType).getBaseType(), _)
|
||||
// Ideally, this predicate should also check that no item of `arr` is ever
|
||||
// reassigned, but such an analysis could get fairly complicated. Instead, we
|
||||
// just hope that nobody would initialize an array of constants and then
|
||||
// overwrite some of them with untrusted data.
|
||||
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
|
||||
@@ -63,156 +52,91 @@ predicate underscoreMacro(Expr e) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a value of character pointer type may flow _directly_ from `src` to
|
||||
* `dst`.
|
||||
* Holds if `t` cannot hold a character array, directly or indirectly.
|
||||
*/
|
||||
predicate stringFlowStep(Expr src, Expr dst) {
|
||||
not underscoreMacro(dst)
|
||||
and
|
||||
stringType(dst.getType(), _)
|
||||
and
|
||||
(
|
||||
src = dst.(VariableAccess).getTarget().getAnAssignedValue()
|
||||
or
|
||||
src = dst.(ConditionalExpr).getThen()
|
||||
or
|
||||
src = dst.(ConditionalExpr).getElse()
|
||||
or
|
||||
src = dst.(AssignExpr).getRValue()
|
||||
or
|
||||
src = dst.(CommaExpr).getRightOperand()
|
||||
or
|
||||
src = dst.(UnaryPlusExpr).getOperand()
|
||||
or
|
||||
stringArray(dst.(ArrayExpr).getArrayBase().(VariableAccess).getTarget(),
|
||||
src.getParent())
|
||||
or
|
||||
// Follow a parameter to its arguments in all callers.
|
||||
exists(Parameter p | p = dst.(VariableAccess).getTarget() |
|
||||
src = p.getFunction().getACallToThisFunction().getArgument(p.getIndex())
|
||||
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.(FunctionCall) |
|
||||
not (
|
||||
whitelistFunction(fc.getTarget(), _) or
|
||||
fc.getTarget().hasDefinition()
|
||||
)
|
||||
)
|
||||
or
|
||||
// Follow a call to a gettext function without looking at its body even if
|
||||
// the body is known. This ensures that we report the error in the relevant
|
||||
// location rather than inside the body of some `_` function.
|
||||
exists(Function gettext, FunctionCall call, int idx |
|
||||
gettextFunction(gettext, idx) and
|
||||
dst = call and
|
||||
gettext = call.getTarget()
|
||||
| src = call.getArgument(idx)
|
||||
exists(Parameter p | p = e.(VariableAccess).getTarget().(Parameter) |
|
||||
p.getFunction().getName() = "main" and p.getType() instanceof PointerType
|
||||
)
|
||||
or
|
||||
// Follow a call to a non-gettext function through its return statements.
|
||||
exists(Function f, ReturnStmt retStmt |
|
||||
f = dst.(FunctionCall).getTarget() and
|
||||
f = retStmt.getEnclosingFunction() and
|
||||
not gettextFunction(f, _)
|
||||
| src = retStmt.getExpr()
|
||||
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
|
||||
}
|
||||
|
||||
/** Holds if `v` may be written to, other than through `AssignExpr`. */
|
||||
predicate nonConstVariable(Variable v) {
|
||||
exists(Type charType |
|
||||
stringType(v.getType(), charType) and not charType.isConst()
|
||||
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 source, DataFlow::Node sink |
|
||||
cf.hasFlow(source, sink) and
|
||||
sink.asExpr() = formatString
|
||||
)
|
||||
or
|
||||
exists(AssignPointerAddExpr e |
|
||||
e.getLValue().(VariableAccess).getTarget() = v
|
||||
)
|
||||
or
|
||||
exists(AssignPointerSubExpr e |
|
||||
e.getLValue().(VariableAccess).getTarget() = v
|
||||
)
|
||||
or
|
||||
exists(CrementOperation e | e.getOperand().(VariableAccess).getTarget() = v)
|
||||
or
|
||||
exists(AddressOfExpr e | e.getOperand().(VariableAccess).getTarget() = v)
|
||||
or
|
||||
exists(ReferenceToExpr e | e.getExpr().(VariableAccess).getTarget() = v)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this expression is _directly_ considered non-constant for the
|
||||
* purpose of this query.
|
||||
*
|
||||
* Each case of `Expr` that may have string type should be listed either in
|
||||
* `nonConst` or `stringFlowStep`. Omitting a case leads to false negatives.
|
||||
* Having a case in both places leads to unnecessary computation.
|
||||
*/
|
||||
predicate nonConst(Expr e) {
|
||||
nonConstVariable(e.(VariableAccess).getTarget())
|
||||
or
|
||||
e instanceof CrementOperation
|
||||
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
|
||||
exists(ArrayExpr ae | ae = e |
|
||||
not stringArray(ae.getArrayBase().(VariableAccess).getTarget(), _)
|
||||
)
|
||||
or
|
||||
e instanceof NewArrayExpr
|
||||
or
|
||||
// e is a call to a function whose definition we cannot see. We assume it is
|
||||
// not constant.
|
||||
exists(Function f | f = e.(FunctionCall).getTarget() |
|
||||
not f.hasDefinition()
|
||||
)
|
||||
or
|
||||
// e is a parameter of a function that's never called. If it were called, we
|
||||
// would instead have followed the call graph in `stringFlowStep`.
|
||||
exists(Function f
|
||||
| f = e.(VariableAccess).getTarget().(Parameter).getFunction()
|
||||
| not exists(f.getACallToThisFunction())
|
||||
)
|
||||
}
|
||||
|
||||
predicate formattingFunctionArgument(
|
||||
FormattingFunction ff, FormattingFunctionCall fc, Expr arg)
|
||||
{
|
||||
fc.getTarget() = ff and
|
||||
fc.getArgument(ff.getFormatParameterIndex()) = arg and
|
||||
// Don't look for errors inside functions that are themselves formatting
|
||||
// functions. We expect that the interesting errors will be in their callers.
|
||||
not fc.getEnclosingFunction() instanceof FormattingFunction
|
||||
}
|
||||
|
||||
// Reflexive-transitive closure of `stringFlow`, restricting the base case to
|
||||
// only consider destination expressions that are arguments to formatting
|
||||
// functions.
|
||||
predicate stringFlow(Expr src, Expr dst) {
|
||||
formattingFunctionArgument(_, _, dst) and src = dst
|
||||
or
|
||||
exists(Expr mid | stringFlowStep(src, mid) and stringFlow(mid, dst))
|
||||
}
|
||||
|
||||
predicate whitelisted(Expr e) {
|
||||
gettextFunction(e.(FunctionCall).getTarget(), _)
|
||||
or
|
||||
underscoreMacro(e)
|
||||
}
|
||||
|
||||
predicate flowFromNonConst(Expr src, Expr dst) {
|
||||
stringFlow(src, dst) and
|
||||
nonConst(src) and
|
||||
not whitelisted(src)
|
||||
}
|
||||
|
||||
from FormattingFunctionCall fc, FormattingFunction ff, Expr format
|
||||
where formattingFunctionArgument(ff, fc, format) and
|
||||
flowFromNonConst(_, format) and
|
||||
not fc.isInMacroExpansion()
|
||||
select format, "The format string argument to " + ff.getName() + " should be constant to prevent security issues and other potential errors."
|
||||
select formatString,
|
||||
"The format string argument to " + call.getTarget().getName() +
|
||||
" should be constant to prevent security issues and other potential errors."
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@ import semmle.code.cpp.models.interfaces.FormattingFunction
|
||||
*/
|
||||
class Printf extends FormattingFunction {
|
||||
Printf() {
|
||||
this instanceof TopLevelFunction and
|
||||
this instanceof TopLevelFunction and
|
||||
(
|
||||
hasGlobalName("printf") or
|
||||
hasGlobalName("printf_s") or
|
||||
@@ -16,7 +16,8 @@ class Printf extends FormattingFunction {
|
||||
not exists(getDefinition().getFile().getRelativePath())
|
||||
}
|
||||
|
||||
override int getFormatParameterIndex() { result=0 }
|
||||
override int getFormatParameterIndex() { result = 0 }
|
||||
|
||||
override predicate isWideCharDefault() {
|
||||
hasGlobalName("wprintf") or
|
||||
hasGlobalName("wprintf_s")
|
||||
@@ -37,9 +38,11 @@ class Fprintf extends FormattingFunction {
|
||||
not exists(getDefinition().getFile().getRelativePath())
|
||||
}
|
||||
|
||||
override int getFormatParameterIndex() { result=1 }
|
||||
override int getFormatParameterIndex() { result = 1 }
|
||||
|
||||
override predicate isWideCharDefault() { hasGlobalName("fwprintf") }
|
||||
override int getOutputParameterIndex() { result=0 }
|
||||
|
||||
override int getOutputParameterIndex() { result = 0 }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,7 +64,12 @@ class Sprintf extends FormattingFunction {
|
||||
}
|
||||
|
||||
override predicate isWideCharDefault() {
|
||||
getParameter(getFormatParameterIndex()).getUnspecifiedType().(PointerType).getBaseType().getSize() > 1
|
||||
getParameter(getFormatParameterIndex())
|
||||
.getType()
|
||||
.getUnspecifiedType()
|
||||
.(PointerType)
|
||||
.getBaseType()
|
||||
.getSize() > 1
|
||||
}
|
||||
|
||||
override int getFormatParameterIndex() {
|
||||
@@ -73,12 +81,12 @@ class Sprintf extends FormattingFunction {
|
||||
getName() != "__builtin___sprintf_chk" and
|
||||
result = 1
|
||||
}
|
||||
override int getOutputParameterIndex() {
|
||||
not hasGlobalName("g_strdup_printf") and result = 0
|
||||
}
|
||||
|
||||
|
||||
override int getOutputParameterIndex() { not hasGlobalName("g_strdup_printf") and result = 0 }
|
||||
|
||||
override int getFirstFormatArgumentIndex() {
|
||||
if hasGlobalName("__builtin___sprintf_chk") then result = 4
|
||||
if hasGlobalName("__builtin___sprintf_chk")
|
||||
then result = 4
|
||||
else result = getNumberOfParameters()
|
||||
}
|
||||
}
|
||||
@@ -89,46 +97,53 @@ class Sprintf extends FormattingFunction {
|
||||
*/
|
||||
class Snprintf extends FormattingFunction {
|
||||
Snprintf() {
|
||||
this instanceof TopLevelFunction and (
|
||||
hasGlobalName("snprintf") // C99 defines snprintf
|
||||
or hasGlobalName("swprintf") // The s version of wide-char printf is also always the n version
|
||||
this instanceof TopLevelFunction and
|
||||
(
|
||||
hasGlobalName("snprintf") or // C99 defines snprintf
|
||||
hasGlobalName("swprintf") or // The s version of wide-char printf is also always the n version
|
||||
// Microsoft has _snprintf as well as several other variations
|
||||
or hasGlobalName("sprintf_s")
|
||||
or hasGlobalName("snprintf_s")
|
||||
or hasGlobalName("swprintf_s")
|
||||
or hasGlobalName("_snprintf")
|
||||
or hasGlobalName("_snprintf_s")
|
||||
or hasGlobalName("_snprintf_l")
|
||||
or hasGlobalName("_snprintf_s_l")
|
||||
or hasGlobalName("_snwprintf")
|
||||
or hasGlobalName("_snwprintf_s")
|
||||
or hasGlobalName("_snwprintf_l")
|
||||
or hasGlobalName("_snwprintf_s_l")
|
||||
or hasGlobalName("_sprintf_s_l")
|
||||
or hasGlobalName("_swprintf_l")
|
||||
or hasGlobalName("_swprintf_s_l")
|
||||
or hasGlobalName("g_snprintf")
|
||||
or hasGlobalName("wnsprintf")
|
||||
or hasGlobalName("__builtin___snprintf_chk")
|
||||
hasGlobalName("sprintf_s") or
|
||||
hasGlobalName("snprintf_s") or
|
||||
hasGlobalName("swprintf_s") or
|
||||
hasGlobalName("_snprintf") or
|
||||
hasGlobalName("_snprintf_s") or
|
||||
hasGlobalName("_snprintf_l") or
|
||||
hasGlobalName("_snprintf_s_l") or
|
||||
hasGlobalName("_snwprintf") or
|
||||
hasGlobalName("_snwprintf_s") or
|
||||
hasGlobalName("_snwprintf_l") or
|
||||
hasGlobalName("_snwprintf_s_l") or
|
||||
hasGlobalName("_sprintf_s_l") or
|
||||
hasGlobalName("_swprintf_l") or
|
||||
hasGlobalName("_swprintf_s_l") or
|
||||
hasGlobalName("g_snprintf") or
|
||||
hasGlobalName("wnsprintf") or
|
||||
hasGlobalName("__builtin___snprintf_chk")
|
||||
) and
|
||||
not exists(getDefinition().getFile().getRelativePath())
|
||||
}
|
||||
|
||||
override int getFormatParameterIndex() {
|
||||
if getName().matches("%\\_l")
|
||||
then result = getFirstFormatArgumentIndex() - 2
|
||||
else result = getFirstFormatArgumentIndex() - 1
|
||||
then result = getFirstFormatArgumentIndex() - 2
|
||||
else result = getFirstFormatArgumentIndex() - 1
|
||||
}
|
||||
|
||||
override predicate isWideCharDefault() {
|
||||
getParameter(getFormatParameterIndex()).getUnspecifiedType().(PointerType).getBaseType().getSize() > 1
|
||||
getParameter(getFormatParameterIndex())
|
||||
.getType()
|
||||
.getUnspecifiedType()
|
||||
.(PointerType)
|
||||
.getBaseType()
|
||||
.getSize() > 1
|
||||
}
|
||||
override int getOutputParameterIndex() { result=0 }
|
||||
|
||||
|
||||
override int getOutputParameterIndex() { result = 0 }
|
||||
|
||||
override int getFirstFormatArgumentIndex() {
|
||||
exists(string name |
|
||||
hasGlobalName(name)
|
||||
and (
|
||||
name = getQualifiedName() and
|
||||
(
|
||||
name = "__builtin___snprintf_chk" and
|
||||
result = 5
|
||||
or
|
||||
@@ -153,9 +168,7 @@ class Snprintf extends FormattingFunction {
|
||||
not exists(getDefinition().getFile().getRelativePath())
|
||||
}
|
||||
|
||||
override int getSizeParameterIndex() {
|
||||
result = 1
|
||||
}
|
||||
override int getSizeParameterIndex() { result = 1 }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -163,36 +176,36 @@ class Snprintf extends FormattingFunction {
|
||||
*/
|
||||
class StringCchPrintf extends FormattingFunction {
|
||||
StringCchPrintf() {
|
||||
this instanceof TopLevelFunction and (
|
||||
hasGlobalName("StringCchPrintf")
|
||||
or hasGlobalName("StringCchPrintfEx")
|
||||
or hasGlobalName("StringCchPrintf_l")
|
||||
or hasGlobalName("StringCchPrintf_lEx")
|
||||
or hasGlobalName("StringCbPrintf")
|
||||
or hasGlobalName("StringCbPrintfEx")
|
||||
or hasGlobalName("StringCbPrintf_l")
|
||||
or hasGlobalName("StringCbPrintf_lEx")
|
||||
this instanceof TopLevelFunction and
|
||||
(
|
||||
hasGlobalName("StringCchPrintf") or
|
||||
hasGlobalName("StringCchPrintfEx") or
|
||||
hasGlobalName("StringCchPrintf_l") or
|
||||
hasGlobalName("StringCchPrintf_lEx") or
|
||||
hasGlobalName("StringCbPrintf") or
|
||||
hasGlobalName("StringCbPrintfEx") or
|
||||
hasGlobalName("StringCbPrintf_l") or
|
||||
hasGlobalName("StringCbPrintf_lEx")
|
||||
) and
|
||||
not exists(getDefinition().getFile().getRelativePath())
|
||||
}
|
||||
|
||||
override int getFormatParameterIndex() {
|
||||
if getName().matches("%Ex")
|
||||
then result = 5
|
||||
else result = 2
|
||||
if getName().matches("%Ex") then result = 5 else result = 2
|
||||
}
|
||||
|
||||
override predicate isWideCharDefault() {
|
||||
getParameter(getFormatParameterIndex()).getUnspecifiedType().(PointerType).getBaseType().getSize() > 1
|
||||
getParameter(getFormatParameterIndex())
|
||||
.getType()
|
||||
.getUnspecifiedType()
|
||||
.(PointerType)
|
||||
.getBaseType()
|
||||
.getSize() > 1
|
||||
}
|
||||
|
||||
override int getOutputParameterIndex() {
|
||||
result = 0
|
||||
}
|
||||
override int getOutputParameterIndex() { result = 0 }
|
||||
|
||||
override int getSizeParameterIndex() {
|
||||
result = 1
|
||||
}
|
||||
override int getSizeParameterIndex() { result = 1 }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -200,11 +213,10 @@ class StringCchPrintf extends FormattingFunction {
|
||||
*/
|
||||
class Syslog extends FormattingFunction {
|
||||
Syslog() {
|
||||
this instanceof TopLevelFunction and (
|
||||
hasGlobalName("syslog")
|
||||
) and
|
||||
this instanceof TopLevelFunction and
|
||||
hasGlobalName("syslog") and
|
||||
not exists(getDefinition().getFile().getRelativePath())
|
||||
}
|
||||
|
||||
override int getFormatParameterIndex() { result=1 }
|
||||
override int getFormatParameterIndex() { result = 1 }
|
||||
}
|
||||
|
||||
@@ -27,23 +27,23 @@ extern char *any_random_function(const char *);
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if(argc > 1)
|
||||
printf(argv[1]); // not ok
|
||||
printf(argv[1]); // BAD
|
||||
else
|
||||
printf("No argument supplied.\n"); // ok
|
||||
printf("No argument supplied.\n"); // GOOD
|
||||
|
||||
printf(_("No argument supplied.\n")); // ok
|
||||
printf(_("No argument supplied.\n")); // GOOD
|
||||
|
||||
printf(dgettext(NULL, "No argument supplied.\n")); // ok
|
||||
printf(dgettext(NULL, "No argument supplied.\n")); // GOOD
|
||||
|
||||
printf(ngettext("One argument\n", "%d arguments\n", argc-1), argc-1); // ok
|
||||
printf(ngettext("One argument\n", "%d arguments\n", argc-1), argc-1); // GOOD
|
||||
|
||||
printf(gettext("%d arguments\n"), argc-1); // ok
|
||||
printf(any_random_function("%d arguments\n"), argc-1); // not ok
|
||||
printf(gettext("%d arguments\n"), argc-1); // GOOD
|
||||
printf(any_random_function("%d arguments\n"), argc-1); // BAD
|
||||
|
||||
// Our query can't look inside the argument to a macro, so it fails to
|
||||
// flag this call.
|
||||
// Even though `_` is mapped to `some_random_function` above,
|
||||
// the following call should not be flagged.
|
||||
printf(_(any_random_function("%d arguments\n")),
|
||||
argc-1); // not ok [NOT REPORTED]
|
||||
argc-1); // GOOD
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
| NonConstantFormat.c:30:10:30:16 | access to array | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| NonConstantFormat.c:41:9:41:27 | call to any_random_function | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| test.cpp:45:10:45:21 | call to make_message | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| test.cpp:50:12:50:16 | hello | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| test.cpp:51:12:51:12 | call to _ | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| test.cpp:52:12:52:18 | call to gettext | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| test.cpp:53:12:53:21 | call to const_wash | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| test.cpp:54:12:54:26 | ... + ... | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| test.cpp:55:12:55:17 | + ... | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| test.cpp:56:12:56:18 | * ... | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| test.cpp:57:12:57:18 | & ... | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| test.cpp:58:12:58:39 | ... + ... | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| test.cpp:60:10:60:35 | ... + ... | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| test.cpp:63:12:63:20 | ... + ... | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| test.cpp:69:12:69:16 | hello | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| test.cpp:75:12:75:16 | hello | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| test.cpp:81:12:81:16 | hello | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| test.cpp:86:12:86:18 | ++ ... | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| test.cpp:93:12:93:16 | hello | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| test.cpp:100:12:100:16 | hello | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| test.cpp:103:12:103:24 | new[] | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| test.cpp:108:12:108:16 | hello | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| test.cpp:117:10:117:19 | call to const_wash | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| nested.cpp:21:23:21:26 | fmt0 | The format string argument to snprintf should be constant to prevent security issues and other potential errors. |
|
||||
| nested.cpp:79:32:79:38 | call to get_fmt | The format string argument to diagnostic should be constant to prevent security issues and other potential errors. |
|
||||
| nested.cpp:87:18:87:20 | fmt | The format string argument to diagnostic should be constant to prevent security issues and other potential errors. |
|
||||
| test.cpp:48:10:48:21 | call to make_message | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| test.cpp:54:12:54:16 | hello | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| test.cpp:57:12:57:21 | call to const_wash | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| test.cpp:58:12:58:26 | ... + ... | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| test.cpp:59:12:59:17 | + ... | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| test.cpp:60:12:60:18 | * ... | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| test.cpp:61:12:61:18 | & ... | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| test.cpp:62:12:62:39 | ... + ... | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| test.cpp:64:10:64:35 | ... + ... | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| test.cpp:67:12:67:20 | ... + ... | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| test.cpp:73:12:73:16 | hello | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| test.cpp:79:12:79:16 | hello | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| test.cpp:85:12:85:16 | hello | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| test.cpp:90:12:90:18 | ++ ... | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| test.cpp:107:12:107:24 | new[] | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
typedef void *va_list;
|
||||
#define va_start(ap, parmN)
|
||||
#define va_end(ap)
|
||||
#define va_arg(ap, type) ((type)0)
|
||||
#define NULL 0
|
||||
|
||||
extern "C" int printf(const char *fmt, ...);
|
||||
extern "C" int snprint(char *buf, int len, const char *fmt, ...);
|
||||
extern "C" int _vsnprintf_s(
|
||||
char *buffer,
|
||||
int sizeOfBuffer,
|
||||
int count,
|
||||
const char *fmt,
|
||||
va_list argptr
|
||||
);
|
||||
extern "C" int snprintf ( char * s, int n, const char * format, ... );
|
||||
|
||||
struct A {
|
||||
void do_print(const char *fmt0) {
|
||||
char buf[32];
|
||||
snprintf(buf, 32, fmt0); // GOOD [FALSE POSITIVE]
|
||||
}
|
||||
};
|
||||
|
||||
struct B {
|
||||
A a;
|
||||
void do_printing(const char *fmt) {
|
||||
a.do_print(fmt);
|
||||
}
|
||||
};
|
||||
|
||||
struct C {
|
||||
B b;
|
||||
void do_some_printing(const char *fmt) {
|
||||
b.do_printing(fmt);
|
||||
}
|
||||
const char *ext_fmt_str(void);
|
||||
};
|
||||
|
||||
void foo(void) {
|
||||
C c;
|
||||
c.do_some_printing(c.ext_fmt_str()); // BAD [NOT DETECTED]
|
||||
}
|
||||
|
||||
struct some_class {
|
||||
// Retrieve some target specific output strings
|
||||
virtual const char * get_fmt() const = 0;
|
||||
};
|
||||
|
||||
struct debug_ {
|
||||
int
|
||||
out_str(
|
||||
const char *fmt,
|
||||
va_list args)
|
||||
{
|
||||
char str[4096];
|
||||
int length = _vsnprintf_s(str, sizeof(str), 0, fmt, args); // GOOD
|
||||
if (length > 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
some_class* some_instance = NULL;
|
||||
debug_ *debug_ctrl;
|
||||
|
||||
void diagnostic(const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
debug_ctrl->out_str(fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void bar(void) {
|
||||
diagnostic (some_instance->get_fmt()); // BAD
|
||||
}
|
||||
|
||||
namespace ns {
|
||||
|
||||
class blab {
|
||||
void out1(void) {
|
||||
char *fmt = (char *)__builtin_alloca(10);
|
||||
diagnostic(fmt); // BAD
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -23,7 +23,7 @@ const char *choose_message(unsigned int n) {
|
||||
|
||||
const char *make_message(unsigned int n) {
|
||||
static char buf[64];
|
||||
sprintf(buf, "%d tasks left\n", n);
|
||||
sprintf(buf, "%d tasks left\n", n); // ok
|
||||
return buf;
|
||||
}
|
||||
|
||||
@@ -41,78 +41,103 @@ const char *const_wash(char *str) {
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
printf(choose_message(argc - 1), argc - 1); // OK
|
||||
printf(make_message(argc - 1)); // NOT OK
|
||||
printf(_("Hello, World\n")); // OK
|
||||
const char *message = messages[2];
|
||||
printf(choose_message(argc - 1), argc - 1); // GOOD
|
||||
printf(messages[1]); // GOOD
|
||||
printf(message); // GOOD
|
||||
printf(make_message(argc - 1)); // BAD
|
||||
printf("Hello, World\n"); // GOOD
|
||||
printf(_("Hello, World\n")); // GOOD
|
||||
{
|
||||
char hello[] = "hello, World\n";
|
||||
hello[0] = 'H';
|
||||
printf(hello); // NOT OK
|
||||
printf(_(hello)); // NOT OK
|
||||
printf(gettext(hello)); // NOT OK
|
||||
printf(const_wash(hello)); // NOT OK
|
||||
printf((hello + 1) + 1); // NOT OK
|
||||
printf(+hello); // NOT OK
|
||||
printf(*&hello); // NOT OK
|
||||
printf(&*hello); // NOT OK
|
||||
printf((char*)(void*)+(hello+1) + 1); // NOT OK
|
||||
printf(hello); // BAD
|
||||
printf(_(hello)); // GOOD
|
||||
printf(gettext(hello)); // GOOD
|
||||
printf(const_wash(hello)); // BAD
|
||||
printf((hello + 1) + 1); // BAD
|
||||
printf(+hello); // BAD
|
||||
printf(*&hello); // BAD
|
||||
printf(&*hello); // BAD
|
||||
printf((char*)(void*)+(hello+1) + 1); // BAD
|
||||
}
|
||||
printf(("Hello, World\n" + 1) + 1); // NOT OK
|
||||
printf(("Hello, World\n" + 1) + 1); // BAD
|
||||
{
|
||||
const char *hello = "Hello, World\n";
|
||||
printf(hello + 1); // NOT OK
|
||||
printf(hello); // OK
|
||||
printf(hello + 1); // BAD
|
||||
printf(hello); // GOOD
|
||||
}
|
||||
{
|
||||
const char *hello = "Hello, World\n";
|
||||
hello += 1;
|
||||
printf(hello); // NOT OK
|
||||
printf(hello); // BAD
|
||||
}
|
||||
{
|
||||
// Same as above block but using "x = x + 1" syntax
|
||||
const char *hello = "Hello, World\n";
|
||||
hello = hello + 1;
|
||||
printf(hello); // NOT OK
|
||||
printf(hello); // BAD
|
||||
}
|
||||
{
|
||||
// Same as above block but using "x++" syntax
|
||||
const char *hello = "Hello, World\n";
|
||||
hello++;
|
||||
printf(hello); // NOT OK
|
||||
printf(hello); // BAD
|
||||
}
|
||||
{
|
||||
// Same as above block but using "++x" as subexpression
|
||||
const char *hello = "Hello, World\n";
|
||||
printf(++hello); // NOT OK
|
||||
printf(++hello); // BAD
|
||||
}
|
||||
{
|
||||
// Same as above block but through a pointer
|
||||
const char *hello = "Hello, World\n";
|
||||
const char **p = &hello;
|
||||
(*p)++;
|
||||
printf(hello); // NOT OK
|
||||
printf(hello); // BAD [NOT DETECTED]
|
||||
}
|
||||
{
|
||||
// Same as above block but through a C++ reference
|
||||
const char *hello = "Hello, World\n";
|
||||
const char *&p = hello;
|
||||
p++;
|
||||
printf(hello); // NOT OK
|
||||
printf(hello); // BAD [NOT DETECTED]
|
||||
}
|
||||
if (gettext_debug) {
|
||||
printf(new char[100]); // NOT OK
|
||||
printf(new char[100]); // BAD
|
||||
}
|
||||
{
|
||||
const char *hello = "Hello, World\n";
|
||||
const char *const *p = &hello; // harmless reference to const pointer
|
||||
printf(hello); // OK [FALSE POSITIVE]
|
||||
printf(hello); // GOOD
|
||||
hello++; // modification comes after use and so does no harm
|
||||
}
|
||||
printf(argc > 2 ? "More than one\n" : _("Only one\n")); // OK
|
||||
printf(argc > 2 ? "More than one\n" : _("Only one\n")); // GOOD
|
||||
|
||||
// This false positive arises because we use const_wash in a problematic
|
||||
// place at one call site, and then the error spreads to all call sites. It
|
||||
// does not happen for "_" only because functions with the name "_" are
|
||||
// special-cased and assumed correct in the query.
|
||||
printf(const_wash("Hello, World\n")); // OK [FALSE POSITIVE]
|
||||
// This following is OK since a const literal is passed to const_wash()
|
||||
// and the taint tracker detects this.
|
||||
//
|
||||
//
|
||||
printf(const_wash("Hello, World\n")); // GOOD
|
||||
}
|
||||
|
||||
const char *simple_func(const char *str) {
|
||||
return str;
|
||||
}
|
||||
|
||||
void another_func(void) {
|
||||
const char *message = messages[2];
|
||||
printf(simple_func("Hello, World\n")); // GOOD
|
||||
printf(messages[1]); // GOOD
|
||||
printf(message); // GOOD
|
||||
printf("Hello, World\n"); // GOOD
|
||||
printf(gettext("Hello, World\n")); // GOOD
|
||||
}
|
||||
|
||||
void set_value_of(int *i);
|
||||
|
||||
void print_ith_message() {
|
||||
int i;
|
||||
set_value_of(&i);
|
||||
printf(messages[i], 1U); // GOOD
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
| consts.cpp:63:9:63:10 | c5 | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| consts.cpp:69:9:69:10 | c6 | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| consts.cpp:81:9:81:10 | c8 | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| consts.cpp:86:9:86:10 | v1 | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
| consts.cpp:91:9:91:10 | v2 | The format string argument to printf should be constant to prevent security issues and other potential errors. |
|
||||
|
||||
@@ -59,12 +59,12 @@ void a() {
|
||||
|
||||
// GOOD: constFunc() always returns a constant string
|
||||
// But we still don't track constantness flow from functions to variables
|
||||
char *c5 = constFunc();
|
||||
char *c5 = constFunc();
|
||||
printf(c5);
|
||||
|
||||
// GOOD: constFunc() always returns a constant string
|
||||
// But we still don't track constantness flow from functions to variables
|
||||
char *c6;
|
||||
char *c6;
|
||||
c6 = constFunc();
|
||||
printf(c6);
|
||||
|
||||
@@ -81,7 +81,7 @@ void a() {
|
||||
printf(c8);
|
||||
|
||||
// BAD: v1 value came from the user
|
||||
char *v1;
|
||||
char v1[100];
|
||||
gets(v1);
|
||||
printf(v1);
|
||||
|
||||
@@ -125,7 +125,7 @@ void a() {
|
||||
// BAD: nonConstFuncToArray() always returns a value from gv1, which is started as constant but was changed to a value that came from the user
|
||||
printf(nonConstFuncToArray(0));
|
||||
|
||||
// BAD: v9 value is copied from v1, which came from the user [NOT DETECTED]
|
||||
// BAD: v9 value is copied from v1, which came from the user
|
||||
const char *v9 = v1;
|
||||
printf(v9);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user