forked from OSchip/llvm-project
[analyzer][StdLibraryFunctionsChecker] Elaborate the summary of fread and fwrite
Add the BufferSize argument constraint to fread and fwrite. This change itself makes it possible to discover a security critical case, described in SEI-CERT ARR38-C. We also add the not-null constraint on the 3rd arguments. In this patch, I also remove those lambdas that don't take any parameters (Fwrite, Fread, Getc), thus making the code better structured. Differential Revision: https://reviews.llvm.org/D87081
This commit is contained in:
parent
e328456a9e
commit
a012bc4c42
|
@ -349,6 +349,9 @@ let ParentPackage = APIModeling in {
|
|||
|
||||
def StdCLibraryFunctionsChecker : Checker<"StdCLibraryFunctions">,
|
||||
HelpText<"Improve modeling of the C standard library functions">,
|
||||
// Uninitialized value check is a mandatory dependency. This Checker asserts
|
||||
// that arguments are always initialized.
|
||||
Dependencies<[CallAndMessageModeling]>,
|
||||
CheckerOptions<[
|
||||
CmdLineOption<Boolean,
|
||||
"DisplayLoadedSummaries",
|
||||
|
|
|
@ -1090,35 +1090,12 @@ void StdLibraryFunctionsChecker::initFunctionSummaries(
|
|||
Optional<QualType> FilePtrRestrictTy = getRestrictTy(FilePtrTy);
|
||||
|
||||
// Templates for summaries that are reused by many functions.
|
||||
auto Getc = [&]() {
|
||||
return Summary(ArgTypes{FilePtrTy}, RetType{IntTy}, NoEvalCall)
|
||||
.Case({ReturnValueCondition(WithinRange,
|
||||
{{EOFv, EOFv}, {0, UCharRangeMax}})});
|
||||
};
|
||||
auto Read = [&](RetType R, RangeInt Max) {
|
||||
return Summary(ArgTypes{Irrelevant, Irrelevant, SizeTy}, RetType{R},
|
||||
NoEvalCall)
|
||||
.Case({ReturnValueCondition(LessThanOrEq, ArgNo(2)),
|
||||
ReturnValueCondition(WithinRange, Range(-1, Max))});
|
||||
};
|
||||
auto Fread = [&]() {
|
||||
return Summary(
|
||||
ArgTypes{VoidPtrRestrictTy, SizeTy, SizeTy, FilePtrRestrictTy},
|
||||
RetType{SizeTy}, NoEvalCall)
|
||||
.Case({
|
||||
ReturnValueCondition(LessThanOrEq, ArgNo(2)),
|
||||
})
|
||||
.ArgConstraint(NotNull(ArgNo(0)));
|
||||
};
|
||||
auto Fwrite = [&]() {
|
||||
return Summary(ArgTypes{ConstVoidPtrRestrictTy, SizeTy, SizeTy,
|
||||
FilePtrRestrictTy},
|
||||
RetType{SizeTy}, NoEvalCall)
|
||||
.Case({
|
||||
ReturnValueCondition(LessThanOrEq, ArgNo(2)),
|
||||
})
|
||||
.ArgConstraint(NotNull(ArgNo(0)));
|
||||
};
|
||||
auto Getline = [&](RetType R, RangeInt Max) {
|
||||
return Summary(ArgTypes{Irrelevant, Irrelevant, Irrelevant}, RetType{R},
|
||||
NoEvalCall)
|
||||
|
@ -1283,19 +1260,45 @@ void StdLibraryFunctionsChecker::initFunctionSummaries(
|
|||
0U, WithinRange, {{EOFv, EOFv}, {0, UCharRangeMax}})));
|
||||
|
||||
// The getc() family of functions that returns either a char or an EOF.
|
||||
addToFunctionSummaryMap("getc", Getc());
|
||||
addToFunctionSummaryMap("fgetc", Getc());
|
||||
addToFunctionSummaryMap(
|
||||
{"getc", "fgetc"}, Signature(ArgTypes{FilePtrTy}, RetType{IntTy}),
|
||||
Summary(NoEvalCall)
|
||||
.Case({ReturnValueCondition(WithinRange,
|
||||
{{EOFv, EOFv}, {0, UCharRangeMax}})}));
|
||||
addToFunctionSummaryMap(
|
||||
"getchar", Summary(ArgTypes{}, RetType{IntTy}, NoEvalCall)
|
||||
.Case({ReturnValueCondition(
|
||||
WithinRange, {{EOFv, EOFv}, {0, UCharRangeMax}})}));
|
||||
|
||||
// read()-like functions that never return more than buffer size.
|
||||
addToFunctionSummaryMap("fread", Fread());
|
||||
addToFunctionSummaryMap("fwrite", Fwrite());
|
||||
auto FreadSummary =
|
||||
Summary(NoEvalCall)
|
||||
.Case({
|
||||
ReturnValueCondition(LessThanOrEq, ArgNo(2)),
|
||||
})
|
||||
.ArgConstraint(NotNull(ArgNo(0)))
|
||||
.ArgConstraint(NotNull(ArgNo(3)))
|
||||
.ArgConstraint(BufferSize(/*Buffer=*/ArgNo(0), /*BufSize=*/ArgNo(1),
|
||||
/*BufSizeMultiplier=*/ArgNo(2)));
|
||||
|
||||
// size_t fread(void *restrict ptr, size_t size, size_t nitems,
|
||||
// FILE *restrict stream);
|
||||
addToFunctionSummaryMap(
|
||||
"fread",
|
||||
Signature(ArgTypes{VoidPtrRestrictTy, SizeTy, SizeTy, FilePtrRestrictTy},
|
||||
RetType{SizeTy}),
|
||||
FreadSummary);
|
||||
// size_t fwrite(const void *restrict ptr, size_t size, size_t nitems,
|
||||
// FILE *restrict stream);
|
||||
addToFunctionSummaryMap("fwrite",
|
||||
Signature(ArgTypes{ConstVoidPtrRestrictTy, SizeTy,
|
||||
SizeTy, FilePtrRestrictTy},
|
||||
RetType{SizeTy}),
|
||||
FreadSummary);
|
||||
|
||||
// We are not sure how ssize_t is defined on every platform, so we
|
||||
// provide three variants that should cover common cases.
|
||||
// FIXME Use lookupTy("ssize_t") instead of the `Read` lambda.
|
||||
// FIXME these are actually defined by POSIX and not by the C standard, we
|
||||
// should handle them together with the rest of the POSIX functions.
|
||||
addToFunctionSummaryMap("read", {Read(IntTy, IntMax), Read(LongTy, LongMax),
|
||||
|
@ -1304,11 +1307,13 @@ void StdLibraryFunctionsChecker::initFunctionSummaries(
|
|||
Read(LongLongTy, LongLongMax)});
|
||||
|
||||
// getline()-like functions either fail or read at least the delimiter.
|
||||
// FIXME Use lookupTy("ssize_t") instead of the `Getline` lambda.
|
||||
// FIXME these are actually defined by POSIX and not by the C standard, we
|
||||
// should handle them together with the rest of the POSIX functions.
|
||||
addToFunctionSummaryMap("getline",
|
||||
{Getline(IntTy, IntMax), Getline(LongTy, LongMax),
|
||||
Getline(LongLongTy, LongLongMax)});
|
||||
// FIXME getdelim's signature is different than getline's!
|
||||
addToFunctionSummaryMap("getdelim",
|
||||
{Getline(IntTy, IntMax), Getline(LongTy, LongMax),
|
||||
Getline(LongLongTy, LongLongMax)});
|
||||
|
|
|
@ -46,8 +46,8 @@ FILE *fopen(const char *path, const char *mode);
|
|||
FILE *tmpfile(void);
|
||||
FILE *freopen(const char *pathname, const char *mode, FILE *stream);
|
||||
int fclose(FILE *fp);
|
||||
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
|
||||
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
|
||||
size_t fread(void *restrict, size_t, size_t, FILE *restrict);
|
||||
size_t fwrite(const void *restrict, size_t, size_t, FILE *restrict);
|
||||
int fputc(int ch, FILE *stream);
|
||||
int fseek(FILE *__stream, long int __off, int __whence);
|
||||
long int ftell(FILE *__stream);
|
||||
|
|
|
@ -6,11 +6,11 @@
|
|||
|
||||
// CHECK: OVERVIEW: Clang Static Analyzer Enabled Checkers List
|
||||
// CHECK-EMPTY:
|
||||
// CHECK-NEXT: core.CallAndMessageModeling
|
||||
// CHECK-NEXT: apiModeling.StdCLibraryFunctions
|
||||
// CHECK-NEXT: apiModeling.TrustNonnull
|
||||
// CHECK-NEXT: apiModeling.llvm.CastValue
|
||||
// CHECK-NEXT: apiModeling.llvm.ReturnValue
|
||||
// CHECK-NEXT: core.CallAndMessageModeling
|
||||
// CHECK-NEXT: core.CallAndMessage
|
||||
// CHECK-NEXT: core.DivideZero
|
||||
// CHECK-NEXT: core.DynamicTypePropagation
|
||||
|
|
|
@ -194,6 +194,22 @@ void test_notnull_symbolic2(FILE *fp, int *buf) {
|
|||
// bugpath-warning{{Function argument constraint is not satisfied}} \
|
||||
// bugpath-note{{Function argument constraint is not satisfied}}
|
||||
}
|
||||
typedef __WCHAR_TYPE__ wchar_t;
|
||||
// This is one test case for the ARR38-C SEI-CERT rule.
|
||||
void ARR38_C_F(FILE *file) {
|
||||
enum { BUFFER_SIZE = 1024 };
|
||||
wchar_t wbuf[BUFFER_SIZE]; // bugpath-note{{'wbuf' initialized here}}
|
||||
|
||||
const size_t size = sizeof(*wbuf);
|
||||
const size_t nitems = sizeof(wbuf);
|
||||
|
||||
// The 3rd parameter should be the number of elements to read, not
|
||||
// the size in bytes.
|
||||
fread(wbuf, size, nitems, file); // \
|
||||
// report-warning{{Function argument constraint is not satisfied}} \
|
||||
// bugpath-warning{{Function argument constraint is not satisfied}} \
|
||||
// bugpath-note{{Function argument constraint is not satisfied}}
|
||||
}
|
||||
|
||||
int __two_constrained_args(int, int);
|
||||
void test_constraints_on_multiple_args(int x, int y) {
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
// Check the case when only the StreamChecker is enabled.
|
||||
// RUN: %clang_analyze_cc1 %s \
|
||||
// RUN: -analyzer-checker=core,alpha.unix.Stream \
|
||||
// RUN: -analyzer-checker=debug.ExprInspection \
|
||||
// RUN: -analyzer-config eagerly-assume=false \
|
||||
// RUN: -triple x86_64-unknown-linux \
|
||||
// RUN: -verify=stream
|
||||
|
||||
// Check the case when only the StdLibraryFunctionsChecker is enabled.
|
||||
// RUN: %clang_analyze_cc1 %s \
|
||||
// RUN: -analyzer-checker=apiModeling.StdCLibraryFunctions \
|
||||
// RUN: -analyzer-config apiModeling.StdCLibraryFunctions:DisplayLoadedSummaries=true \
|
||||
// RUN: -analyzer-checker=debug.ExprInspection \
|
||||
// RUN: -analyzer-config eagerly-assume=false \
|
||||
// RUN: -triple x86_64-unknown-linux \
|
||||
// RUN: -verify=stdLib 2>&1 | FileCheck %s
|
||||
|
||||
// Check the case when both the StreamChecker and the
|
||||
// StdLibraryFunctionsChecker are enabled.
|
||||
// RUN: %clang_analyze_cc1 %s \
|
||||
// RUN: -analyzer-checker=core,alpha.unix.Stream \
|
||||
// RUN: -analyzer-checker=apiModeling.StdCLibraryFunctions \
|
||||
// RUN: -analyzer-config apiModeling.StdCLibraryFunctions:DisplayLoadedSummaries=true \
|
||||
// RUN: -analyzer-checker=debug.ExprInspection \
|
||||
// RUN: -analyzer-config eagerly-assume=false \
|
||||
// RUN: -triple x86_64-unknown-linux \
|
||||
// RUN: -verify=both 2>&1 | FileCheck %s
|
||||
|
||||
// Verify that the summaries are loaded when the StdLibraryFunctionsChecker is
|
||||
// enabled.
|
||||
// CHECK: Loaded summary for: int getchar()
|
||||
// CHECK-NEXT: Loaded summary for: unsigned long fread(void *restrict, size_t, size_t, FILE *restrict)
|
||||
// CHECK-NEXT: Loaded summary for: unsigned long fwrite(const void *restrict, size_t, size_t, FILE *restrict)
|
||||
|
||||
#include "Inputs/system-header-simulator.h"
|
||||
|
||||
void clang_analyzer_eval(int);
|
||||
|
||||
void test_fread_fwrite(FILE *fp, int *buf) {
|
||||
fp = fopen("foo", "r");
|
||||
if (!fp)
|
||||
return;
|
||||
size_t x = fwrite(buf, sizeof(int), 10, fp);
|
||||
|
||||
clang_analyzer_eval(x <= 10); // \
|
||||
// stream-warning{{TRUE}} \
|
||||
// stdLib-warning{{TRUE}} \
|
||||
// both-warning{{TRUE}} \
|
||||
|
||||
clang_analyzer_eval(x == 10); // \
|
||||
// stream-warning{{TRUE}} \
|
||||
// stream-warning{{FALSE}} \
|
||||
// stdLib-warning{{UNKNOWN}} \
|
||||
// both-warning{{TRUE}} \
|
||||
// both-warning{{FALSE}}
|
||||
|
||||
fclose(fp);
|
||||
}
|
Loading…
Reference in New Issue