Merge pull request #1251 from zlaski-semmle/zlaski/cpp370

[CPP-370] Non-constant `format` arguments to `printf` and friends
This commit is contained in:
Jonas Jensen
2019-07-01 14:43:19 +02:00
committed by GitHub
10 changed files with 777 additions and 697 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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 }
}

View File

@@ -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;
}

View File

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

View File

@@ -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
}
};
}

View File

@@ -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
}

View File

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

View File

@@ -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);