[analyzer] New checker for detecting usages of unsafe I/O functions

There are certain unsafe or deprecated (since C11) buffer handling
functions which should be avoided in safety critical code. They
could cause buffer overflows. A new checker,
'security.insecureAPI.DeprecatedOrUnsafeBufferHandling' warns for
every occurrence of such functions (unsafe or deprecated printf,
scanf family, and other buffer handling functions, which now have
a secure variant).

Patch by Dániel Kolozsvári!

Differential Revision: https://reviews.llvm.org/D35068

llvm-svn: 353698
This commit is contained in:
Kristof Umann 2019-02-11 13:46:43 +00:00
parent 0783406120
commit 8d23999639
4 changed files with 195 additions and 3 deletions

View File

@ -566,6 +566,17 @@ security.insecureAPI.vfork (C)
vfork(); // warn
}
security.insecureAPI.DeprecatedOrUnsafeBufferHandling (C)
""""""""""""""""""""""""""""""
Warn on occurrences of unsafe or deprecated buffer handling functions, which now have a secure variant: ``sprintf, vsprintf, scanf, wscanf, fscanf, fwscanf, vscanf, vwscanf, vfscanf, vfwscanf, sscanf, swscanf, vsscanf, vswscanf, swprintf, snprintf, vswprintf, vsnprintf, memcpy, memmove, strncpy, strncat, memset``
.. code-block:: c
void test() {
char buf [5];
strncpy(buf, "a", 1); // warn
}
.. _unix-checkers:
unix

View File

@ -622,6 +622,13 @@ def UncheckedReturn : Checker<"UncheckedReturn">,
Dependencies<[SecuritySyntaxChecker]>,
Documentation<HasDocumentation>;
def DeprecatedOrUnsafeBufferHandling :
Checker<"DeprecatedOrUnsafeBufferHandling">,
HelpText<"Warn on uses of unsecure or deprecated buffer manipulating "
"functions">,
Dependencies<[SecuritySyntaxChecker]>,
Documentation<HasDocumentation>;
} // end "security.insecureAPI"
let ParentPackage = Security in {

View File

@ -44,6 +44,7 @@ struct ChecksFilter {
DefaultBool check_mktemp;
DefaultBool check_mkstemp;
DefaultBool check_strcpy;
DefaultBool check_DeprecatedOrUnsafeBufferHandling;
DefaultBool check_rand;
DefaultBool check_vfork;
DefaultBool check_FloatLoopCounter;
@ -57,6 +58,7 @@ struct ChecksFilter {
CheckName checkName_mktemp;
CheckName checkName_mkstemp;
CheckName checkName_strcpy;
CheckName checkName_DeprecatedOrUnsafeBufferHandling;
CheckName checkName_rand;
CheckName checkName_vfork;
CheckName checkName_FloatLoopCounter;
@ -103,6 +105,8 @@ public:
void checkCall_mkstemp(const CallExpr *CE, const FunctionDecl *FD);
void checkCall_strcpy(const CallExpr *CE, const FunctionDecl *FD);
void checkCall_strcat(const CallExpr *CE, const FunctionDecl *FD);
void checkDeprecatedOrUnsafeBufferHandling(const CallExpr *CE,
const FunctionDecl *FD);
void checkCall_rand(const CallExpr *CE, const FunctionDecl *FD);
void checkCall_random(const CallExpr *CE, const FunctionDecl *FD);
void checkCall_vfork(const CallExpr *CE, const FunctionDecl *FD);
@ -148,6 +152,14 @@ void WalkAST::VisitCallExpr(CallExpr *CE) {
.Case("mkstemps", &WalkAST::checkCall_mkstemp)
.Cases("strcpy", "__strcpy_chk", &WalkAST::checkCall_strcpy)
.Cases("strcat", "__strcat_chk", &WalkAST::checkCall_strcat)
.Cases("sprintf", "vsprintf", "scanf", "wscanf", "fscanf", "fwscanf",
"vscanf", "vwscanf", "vfscanf", "vfwscanf",
&WalkAST::checkDeprecatedOrUnsafeBufferHandling)
.Cases("sscanf", "swscanf", "vsscanf", "vswscanf", "swprintf",
"snprintf", "vswprintf", "vsnprintf", "memcpy", "memmove",
&WalkAST::checkDeprecatedOrUnsafeBufferHandling)
.Cases("strncpy", "strncat", "memset",
&WalkAST::checkDeprecatedOrUnsafeBufferHandling)
.Case("drand48", &WalkAST::checkCall_rand)
.Case("erand48", &WalkAST::checkCall_rand)
.Case("jrand48", &WalkAST::checkCall_rand)
@ -552,7 +564,6 @@ void WalkAST::checkCall_mktemp(const CallExpr *CE, const FunctionDecl *FD) {
CELoc, CE->getCallee()->getSourceRange());
}
//===----------------------------------------------------------------------===//
// Check: Use of 'mkstemp', 'mktemp', 'mkdtemp' should contain at least 6 X's.
//===----------------------------------------------------------------------===//
@ -641,6 +652,7 @@ void WalkAST::checkCall_mkstemp(const CallExpr *CE, const FunctionDecl *FD) {
// CWE-119: Improper Restriction of Operations within
// the Bounds of a Memory Buffer
//===----------------------------------------------------------------------===//
void WalkAST::checkCall_strcpy(const CallExpr *CE, const FunctionDecl *FD) {
if (!filter.check_strcpy)
return;
@ -679,6 +691,7 @@ void WalkAST::checkCall_strcpy(const CallExpr *CE, const FunctionDecl *FD) {
// CWE-119: Improper Restriction of Operations within
// the Bounds of a Memory Buffer
//===----------------------------------------------------------------------===//
void WalkAST::checkCall_strcat(const CallExpr *CE, const FunctionDecl *FD) {
if (!filter.check_strcpy)
return;
@ -700,9 +713,89 @@ void WalkAST::checkCall_strcat(const CallExpr *CE, const FunctionDecl *FD) {
CELoc, CE->getCallee()->getSourceRange());
}
//===----------------------------------------------------------------------===//
// Check: Any use of 'sprintf', 'vsprintf', 'scanf', 'wscanf', 'fscanf',
// 'fwscanf', 'vscanf', 'vwscanf', 'vfscanf', 'vfwscanf', 'sscanf',
// 'swscanf', 'vsscanf', 'vswscanf', 'swprintf', 'snprintf', 'vswprintf',
// 'vsnprintf', 'memcpy', 'memmove', 'strncpy', 'strncat', 'memset'
// is deprecated since C11.
//
// Use of 'sprintf', 'vsprintf', 'scanf', 'wscanf','fscanf',
// 'fwscanf', 'vscanf', 'vwscanf', 'vfscanf', 'vfwscanf', 'sscanf',
// 'swscanf', 'vsscanf', 'vswscanf' without buffer limitations
// is insecure.
//
// CWE-119: Improper Restriction of Operations within
// the Bounds of a Memory Buffer
//===----------------------------------------------------------------------===//
void WalkAST::checkDeprecatedOrUnsafeBufferHandling(const CallExpr *CE,
const FunctionDecl *FD) {
if (!filter.check_DeprecatedOrUnsafeBufferHandling)
return;
if (!BR.getContext().getLangOpts().C11)
return;
// Issue a warning. ArgIndex == -1: Deprecated but not unsafe (has size
// restrictions).
enum { DEPR_ONLY = -1, UNKNOWN_CALL = -2 };
StringRef Name = FD->getIdentifier()->getName();
int ArgIndex =
llvm::StringSwitch<int>(Name)
.Cases("scanf", "wscanf", "vscanf", "vwscanf", 0)
.Cases("sprintf", "vsprintf", "fscanf", "fwscanf", "vfscanf",
"vfwscanf", "sscanf", "swscanf", "vsscanf", "vswscanf", 1)
.Cases("swprintf", "snprintf", "vswprintf", "vsnprintf", "memcpy",
"memmove", "memset", "strncpy", "strncat", DEPR_ONLY)
.Default(UNKNOWN_CALL);
assert(ArgIndex != UNKNOWN_CALL && "Unsupported function");
bool BoundsProvided = ArgIndex == DEPR_ONLY;
if (!BoundsProvided) {
// Currently we only handle (not wide) string literals. It is possible to do
// better, either by looking at references to const variables, or by doing
// real flow analysis.
auto FormatString =
dyn_cast<StringLiteral>(CE->getArg(ArgIndex)->IgnoreParenImpCasts());
if (FormatString &&
FormatString->getString().find("%s") == StringRef::npos &&
FormatString->getString().find("%[") == StringRef::npos)
BoundsProvided = true;
}
SmallString<128> Buf1;
SmallString<512> Buf2;
llvm::raw_svector_ostream Out1(Buf1);
llvm::raw_svector_ostream Out2(Buf2);
Out1 << "Potential insecure memory buffer bounds restriction in call '"
<< Name << "'";
Out2 << "Call to function '" << Name
<< "' is insecure as it does not provide ";
if (!BoundsProvided) {
Out2 << "bounding of the memory buffer or ";
}
Out2 << "security checks introduced "
"in the C11 standard. Replace with analogous functions that "
"support length arguments or provides boundary checks such as '"
<< Name << "_s' in case of C11";
PathDiagnosticLocation CELoc =
PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC);
BR.EmitBasicReport(AC->getDecl(),
filter.checkName_DeprecatedOrUnsafeBufferHandling,
Out1.str(), "Security", Out2.str(), CELoc,
CE->getCallee()->getSourceRange());
}
//===----------------------------------------------------------------------===//
// Common check for str* functions with no bounds parameters.
//===----------------------------------------------------------------------===//
bool WalkAST::checkCall_strCommon(const CallExpr *CE, const FunctionDecl *FD) {
const FunctionProtoType *FPT = FD->getType()->getAs<FunctionProtoType>();
if (!FPT)
@ -936,5 +1029,4 @@ REGISTER_CHECKER(rand)
REGISTER_CHECKER(vfork)
REGISTER_CHECKER(FloatLoopCounter)
REGISTER_CHECKER(UncheckedReturn)
REGISTER_CHECKER(DeprecatedOrUnsafeBufferHandling)

View File

@ -13,6 +13,9 @@
# define BUILTIN(f) f
#endif /* USE_BUILTINS */
#include "Inputs/system-header-simulator-for-valist.h"
#include "Inputs/system-header-simulator-for-simple-stream.h"
typedef typeof(sizeof(int)) size_t;
@ -238,3 +241,82 @@ void test_mkstemp() {
mkdtemp("XXXXXX");
}
//===----------------------------------------------------------------------===
// deprecated or unsafe buffer handling
//===----------------------------------------------------------------------===
typedef int wchar_t;
int sprintf(char *str, const char *format, ...);
//int vsprintf (char *s, const char *format, va_list arg);
int scanf(const char *format, ...);
int wscanf(const wchar_t *format, ...);
int fscanf(FILE *stream, const char *format, ...);
int fwscanf(FILE *stream, const wchar_t *format, ...);
int vscanf(const char *format, va_list arg);
int vwscanf(const wchar_t *format, va_list arg);
int vfscanf(FILE *stream, const char *format, va_list arg);
int vfwscanf(FILE *stream, const wchar_t *format, va_list arg);
int sscanf(const char *s, const char *format, ...);
int swscanf(const wchar_t *ws, const wchar_t *format, ...);
int vsscanf(const char *s, const char *format, va_list arg);
int vswscanf(const wchar_t *ws, const wchar_t *format, va_list arg);
int swprintf(wchar_t *ws, size_t len, const wchar_t *format, ...);
int snprintf(char *s, size_t n, const char *format, ...);
int vswprintf(wchar_t *ws, size_t len, const wchar_t *format, va_list arg);
int vsnprintf(char *s, size_t n, const char *format, va_list arg);
void *memcpy(void *destination, const void *source, size_t num);
void *memmove(void *destination, const void *source, size_t num);
char *strncpy(char *destination, const char *source, size_t num);
char *strncat(char *destination, const char *source, size_t num);
void *memset(void *ptr, int value, size_t num);
void test_deprecated_or_unsafe_buffer_handling_1() {
char buf [5];
wchar_t wbuf [5];
int a;
FILE *file;
sprintf(buf, "a"); // expected-warning{{Call to function 'sprintf' is insecure}}
scanf("%d", &a); // expected-warning{{Call to function 'scanf' is insecure}}
scanf("%s", buf); // expected-warning{{Call to function 'scanf' is insecure}}
scanf("%4s", buf); // expected-warning{{Call to function 'scanf' is insecure}}
wscanf((const wchar_t*) L"%s", buf); // expected-warning{{Call to function 'wscanf' is insecure}}
fscanf(file, "%d", &a); // expected-warning{{Call to function 'fscanf' is insecure}}
fscanf(file, "%s", buf); // expected-warning{{Call to function 'fscanf' is insecure}}
fscanf(file, "%4s", buf); // expected-warning{{Call to function 'fscanf' is insecure}}
fwscanf(file, (const wchar_t*) L"%s", wbuf); // expected-warning{{Call to function 'fwscanf' is insecure}}
sscanf("5", "%d", &a); // expected-warning{{Call to function 'sscanf' is insecure}}
sscanf("5", "%s", buf); // expected-warning{{Call to function 'sscanf' is insecure}}
sscanf("5", "%4s", buf); // expected-warning{{Call to function 'sscanf' is insecure}}
swscanf(L"5", (const wchar_t*) L"%s", wbuf); // expected-warning{{Call to function 'swscanf' is insecure}}
swprintf(L"5", 1, (const wchar_t*) L"%s", wbuf); // expected-warning{{Call to function 'swprintf' is insecure}}
snprintf("5", 1, "%s", buf); // expected-warning{{Call to function 'snprintf' is insecure}}
memcpy(buf, wbuf, 1); // expected-warning{{Call to function 'memcpy' is insecure}}
memmove(buf, wbuf, 1); // expected-warning{{Call to function 'memmove' is insecure}}
strncpy(buf, "a", 1); // expected-warning{{Call to function 'strncpy' is insecure}}
strncat(buf, "a", 1); // expected-warning{{Call to function 'strncat' is insecure}}
memset(buf, 'a', 1); // expected-warning{{Call to function 'memset' is insecure}}
}
void test_deprecated_or_unsafe_buffer_handling_2(const char *format, ...) {
char buf [5];
FILE *file;
va_list args;
va_start(args, format);
vsprintf(buf, format, args); // expected-warning{{Call to function 'vsprintf' is insecure}}
vscanf(format, args); // expected-warning{{Call to function 'vscanf' is insecure}}
vfscanf(file, format, args); // expected-warning{{Call to function 'vfscanf' is insecure}}
vsscanf("a", format, args); // expected-warning{{Call to function 'vsscanf' is insecure}}
vsnprintf("a", 1, format, args); // expected-warning{{Call to function 'vsnprintf' is insecure}}
}
void test_deprecated_or_unsafe_buffer_handling_3(const wchar_t *format, ...) {
wchar_t wbuf [5];
FILE *file;
va_list args;
va_start(args, format);
vwscanf(format, args); // expected-warning{{Call to function 'vwscanf' is insecure}}
vfwscanf(file, format, args); // expected-warning{{Call to function 'vfwscanf' is insecure}}
vswscanf(L"a", format, args); // expected-warning{{Call to function 'vswscanf' is insecure}}
vswprintf(L"a", 1, format, args); // expected-warning{{Call to function 'vswprintf' is insecure}}
}