Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,8 @@ Static Analyzer
New features
^^^^^^^^^^^^

- Now CSA models `builtin_*_overflow` functions.

- MallocChecker now checks for ``ownership_returns(class, idx)`` and ``ownership_takes(class, idx)``
attributes with class names different from "malloc". Clang static analyzer now reports an error
if class of allocation and deallocation function mismatches.
Expand Down
172 changes: 170 additions & 2 deletions clang/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,89 @@

#include "clang/Basic/Builtins.h"
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Checkers/Taint.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h"

using namespace clang;
using namespace ento;
using namespace taint;

namespace {

QualType getSufficientTypeForOverflowOp(CheckerContext &C, const QualType &T) {
// Calling a builtin with a non-integer type result produces compiler error.
assert(T->isIntegerType());

ASTContext &ACtx = C.getASTContext();

unsigned BitWidth = ACtx.getIntWidth(T);
return ACtx.getIntTypeForBitwidth(BitWidth * 2, T->isSignedIntegerType());
}

QualType getOverflowBuiltinResultType(const CallEvent &Call) {
// Calling a builtin with an incorrect argument count produces compiler error.
assert(Call.getNumArgs() == 3);

return Call.getArgExpr(2)->getType()->getPointeeType();
}

QualType getOverflowBuiltinResultType(const CallEvent &Call, CheckerContext &C,
unsigned BI) {
// Calling a builtin with an incorrect argument count produces compiler error.
assert(Call.getNumArgs() == 3);

ASTContext &ACtx = C.getASTContext();

switch (BI) {
case Builtin::BI__builtin_smul_overflow:
case Builtin::BI__builtin_ssub_overflow:
case Builtin::BI__builtin_sadd_overflow:
return ACtx.IntTy;
case Builtin::BI__builtin_smull_overflow:
case Builtin::BI__builtin_ssubl_overflow:
case Builtin::BI__builtin_saddl_overflow:
return ACtx.LongTy;
case Builtin::BI__builtin_smulll_overflow:
case Builtin::BI__builtin_ssubll_overflow:
case Builtin::BI__builtin_saddll_overflow:
return ACtx.LongLongTy;
case Builtin::BI__builtin_umul_overflow:
case Builtin::BI__builtin_usub_overflow:
case Builtin::BI__builtin_uadd_overflow:
return ACtx.UnsignedIntTy;
case Builtin::BI__builtin_umull_overflow:
case Builtin::BI__builtin_usubl_overflow:
case Builtin::BI__builtin_uaddl_overflow:
return ACtx.UnsignedLongTy;
case Builtin::BI__builtin_umulll_overflow:
case Builtin::BI__builtin_usubll_overflow:
case Builtin::BI__builtin_uaddll_overflow:
return ACtx.UnsignedLongLongTy;
case Builtin::BI__builtin_mul_overflow:
case Builtin::BI__builtin_sub_overflow:
case Builtin::BI__builtin_add_overflow:
return getOverflowBuiltinResultType(Call);
default:
assert(false && "Unknown overflow builtin");
return ACtx.IntTy;
}
}

class BuiltinFunctionChecker : public Checker<eval::Call> {
public:
bool evalCall(const CallEvent &Call, CheckerContext &C) const;
void handleOverflowBuiltin(const CallEvent &Call, CheckerContext &C,
BinaryOperator::Opcode Op,
QualType ResultType) const;
std::pair<bool, bool> checkOverflow(CheckerContext &C, SVal RetVal,
QualType Res) const;

private:
// From: clang/include/clang/Basic/Builtins.def
Expand All @@ -50,6 +118,75 @@ class BuiltinFunctionChecker : public Checker<eval::Call> {

} // namespace

std::pair<bool, bool>
BuiltinFunctionChecker::checkOverflow(CheckerContext &C, SVal RetVal,
QualType Res) const {
ProgramStateRef State = C.getState();
SValBuilder &SVB = C.getSValBuilder();
ASTContext &ACtx = C.getASTContext();

// Calling a builtin with a non-integer type result produces compiler error.
assert(Res->isIntegerType());

unsigned BitWidth = ACtx.getIntWidth(Res);
auto MinVal =
llvm::APSInt::getMinValue(BitWidth, Res->isUnsignedIntegerType());
auto MaxVal =
llvm::APSInt::getMaxValue(BitWidth, Res->isUnsignedIntegerType());

SVal IsLeMax =
SVB.evalBinOp(State, BO_LE, RetVal, nonloc::ConcreteInt(MaxVal), Res);
SVal IsGeMin =
SVB.evalBinOp(State, BO_GE, RetVal, nonloc::ConcreteInt(MinVal), Res);

auto [MayNotOverflow, MayOverflow] =
State->assume(IsLeMax.castAs<DefinedOrUnknownSVal>());
auto [MayNotUnderflow, MayUnderflow] =
State->assume(IsGeMin.castAs<DefinedOrUnknownSVal>());

return {MayOverflow || MayUnderflow, MayNotOverflow && MayNotUnderflow};
}

void BuiltinFunctionChecker::handleOverflowBuiltin(const CallEvent &Call,
CheckerContext &C,
BinaryOperator::Opcode Op,
QualType ResultType) const {
// Calling a builtin with an incorrect argument count produces compiler error.
assert(Call.getNumArgs() == 3);

ProgramStateRef State = C.getState();
SValBuilder &SVB = C.getSValBuilder();
const Expr *CE = Call.getOriginExpr();

SVal Arg1 = Call.getArgSVal(0);
SVal Arg2 = Call.getArgSVal(1);

SVal RetValMax = SVB.evalBinOp(State, Op, Arg1, Arg2,
getSufficientTypeForOverflowOp(C, ResultType));
SVal RetVal = SVB.evalBinOp(State, Op, Arg1, Arg2, ResultType);

auto [Overflow, NotOverflow] = checkOverflow(C, RetValMax, ResultType);
if (NotOverflow) {
ProgramStateRef StateNoOverflow =
State->BindExpr(CE, C.getLocationContext(), SVB.makeTruthVal(false));

if (auto L = Call.getArgSVal(2).getAs<Loc>()) {
StateNoOverflow =
StateNoOverflow->bindLoc(*L, RetVal, C.getLocationContext());

// Propagate taint if any of the argumets were tainted
if (isTainted(State, Arg1) || isTainted(State, Arg2))
StateNoOverflow = addTaint(StateNoOverflow, *L);
}

C.addTransition(StateNoOverflow);
}

if (Overflow)
C.addTransition(
State->BindExpr(CE, C.getLocationContext(), SVB.makeTruthVal(true)));
}

bool BuiltinFunctionChecker::isBuiltinLikeFunction(
const CallEvent &Call) const {
const auto *FD = llvm::dyn_cast_or_null<FunctionDecl>(Call.getDecl());
Expand Down Expand Up @@ -82,10 +219,41 @@ bool BuiltinFunctionChecker::evalCall(const CallEvent &Call,
return true;
}

switch (FD->getBuiltinID()) {
unsigned BI = FD->getBuiltinID();

switch (BI) {
default:
return false;

case Builtin::BI__builtin_mul_overflow:
case Builtin::BI__builtin_smul_overflow:
case Builtin::BI__builtin_smull_overflow:
case Builtin::BI__builtin_smulll_overflow:
case Builtin::BI__builtin_umul_overflow:
case Builtin::BI__builtin_umull_overflow:
case Builtin::BI__builtin_umulll_overflow:
handleOverflowBuiltin(Call, C, BO_Mul,
getOverflowBuiltinResultType(Call, C, BI));
return true;
case Builtin::BI__builtin_sub_overflow:
case Builtin::BI__builtin_ssub_overflow:
case Builtin::BI__builtin_ssubl_overflow:
case Builtin::BI__builtin_ssubll_overflow:
case Builtin::BI__builtin_usub_overflow:
case Builtin::BI__builtin_usubl_overflow:
case Builtin::BI__builtin_usubll_overflow:
handleOverflowBuiltin(Call, C, BO_Sub,
getOverflowBuiltinResultType(Call, C, BI));
return true;
case Builtin::BI__builtin_add_overflow:
case Builtin::BI__builtin_sadd_overflow:
case Builtin::BI__builtin_saddl_overflow:
case Builtin::BI__builtin_saddll_overflow:
case Builtin::BI__builtin_uadd_overflow:
case Builtin::BI__builtin_uaddl_overflow:
case Builtin::BI__builtin_uaddll_overflow:
handleOverflowBuiltin(Call, C, BO_Add,
getOverflowBuiltinResultType(Call, C, BI));
return true;
case Builtin::BI__builtin_assume:
case Builtin::BI__assume: {
assert (Call.getNumArgs() > 0);
Expand Down
157 changes: 157 additions & 0 deletions clang/test/Analysis/builtin_overflow.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
// RUN: %clang_analyze_cc1 -triple x86_64-unknown-unknown -verify %s \
// RUN: -analyzer-checker=core,debug.ExprInspection

#define __UINT_MAX__ (__INT_MAX__ * 2U + 1U)
#define __INT_MIN__ (-__INT_MAX__ - 1)

void clang_analyzer_dump_int(int);
void clang_analyzer_dump_long(long);
void clang_analyzer_eval(int);
void clang_analyzer_warnIfReached(void);

void test_add_nooverflow(void)
{
int res;

if (__builtin_add_overflow(10, 20, &res)) {
clang_analyzer_warnIfReached();
return;
}

clang_analyzer_dump_int(res); //expected-warning{{30 S32b}}
}

void test_add_overflow(void)
{
int res;

if (__builtin_add_overflow(__INT_MAX__, 1, &res)) {
clang_analyzer_dump_int(res); //expected-warning{{1st function call argument is an uninitialized value}}
return;
}

clang_analyzer_warnIfReached();
}

void test_add_underoverflow(void)
{
int res;

if (__builtin_add_overflow(__INT_MIN__, -1, &res)) {
clang_analyzer_dump_int(res); //expected-warning{{1st function call argument is an uninitialized value}}
return;
}

clang_analyzer_warnIfReached();
}

void test_sub_underflow(void)
{
int res;

if (__builtin_sub_overflow(__INT_MIN__, 10, &res)) {
return;
}

clang_analyzer_warnIfReached();
}

void test_sub_overflow(void)
{
int res;

if (__builtin_sub_overflow(__INT_MAX__, -1, &res)) {
return;
}

clang_analyzer_warnIfReached();
}

void test_sub_nooverflow(void)
{
int res;

if (__builtin_sub_overflow(__INT_MAX__, 1, &res)) {
clang_analyzer_warnIfReached();
return;
}

clang_analyzer_dump_int(res); //expected-warning{{2147483646 S32b}}
}

void test_mul_overrflow(void)
{
int res;

if (__builtin_mul_overflow(__INT_MAX__, 2, &res)) {
return;
}

clang_analyzer_warnIfReached();
}

void test_mul_underrflow(void)
{
int res;

if (__builtin_mul_overflow(__INT_MIN__, -2, &res)) {
return;
}

clang_analyzer_warnIfReached();
}

void test_mul_nooverflow(void)
{
int res;

if (__builtin_mul_overflow(10, -2, &res)) {
clang_analyzer_warnIfReached();
return;
}

clang_analyzer_dump_int(res); //expected-warning{{-20 S32b}}
}

void test_nooverflow_diff_types(void)
{
long res;

// This is not an overflow, since result type is long.
if (__builtin_add_overflow(__INT_MAX__, 1, &res)) {
clang_analyzer_warnIfReached();
return;
}

clang_analyzer_dump_long(res); //expected-warning{{2147483648 S64b}}
}

void test_uaddll_overflow_contraints(unsigned long a, unsigned long b)
{
unsigned long long res;

if (a != 10)
return;
if (b != 10)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Such constraints we call "perfect constraints" as they narrow down the value domain to a single value. After this, the engine will fold all uses of this into a concreteInt, eliminating the symbolic value.

Consequently, perfect constraints are different to common constraints. They should have separate tests.

That said, two symbolic values with half side constraints are missing. x>=(int max -2, y>= 10 for instance.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tried to add smth like this, but it fails even for more simple constraint like if (a > 10 || b > 10) return; for unsigned case. See #102602 (comment).

return;

if (__builtin_uaddll_overflow(a, b, &res)) {
clang_analyzer_warnIfReached();
return;
}
}

void test_uadd_overflow_contraints(unsigned a, unsigned b)
{
unsigned res;

if (a > 5)
return;
if (b != 10)
return;

if (__builtin_uadd_overflow(a, b, &res)) {
clang_analyzer_warnIfReached();
return;
}
}
15 changes: 15 additions & 0 deletions clang/test/Analysis/out-of-bounds-diagnostics.c
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,21 @@ int *mallocRegion(void) {
return mem;
}

int *custom_calloc(size_t a, size_t b) {
size_t res;

return __builtin_mul_overflow(a, b, &res) ? 0 : malloc(res);
}

int *mallocRegionOverflow(void) {
int *mem = (int*)custom_calloc(10, sizeof(int));

mem[20] = 10;
// expected-warning@-1 {{Out of bound access to memory after the end of the heap area}}
// expected-note@-2 {{Access of the heap area at index 20, while it holds only 10 'int' elements}}
return mem;
}

int *mallocRegionDeref(void) {
int *mem = (int*)malloc(2*sizeof(int));

Expand Down
Loading