Array index: Checking array index out of bounds for dynamic buffers

This commit is contained in:
Daniel Marjamäki 2019-03-17 13:09:15 +01:00
parent 18668a52b9
commit 92f4113b59
8 changed files with 155 additions and 49 deletions

View File

@ -60,6 +60,27 @@ static const CWE CWE788(788U); // Access of Memory Location After End of Buffer
//---------------------------------------------------------------------------
static std::vector<Dimension> getDynamicDimensions(const Token *tok, MathLib::bigint typeSize)
{
if (typeSize == 0) {
const std::vector<Dimension> dimensions;
return dimensions;
}
for (const ValueFlow::Value &value : tok->values()) {
if (!value.isBufferSizeValue())
continue;
Dimension dim;
dim.tok = nullptr;
dim.num = value.intvalue / typeSize;
dim.known = value.isKnown();
const std::vector<Dimension> dimensions{dim};
return dimensions;
}
const std::vector<Dimension> dimensions;
return dimensions;
}
static size_t getMinFormatStringOutputLength(const std::vector<const Token*> &parameters, unsigned int formatStringArgNr)
{
if (formatStringArgNr == 0 || formatStringArgNr > parameters.size())
@ -196,18 +217,32 @@ void CheckBufferOverrun::arrayIndex()
if (!indexToken)
continue;
const Token *stringLiteral = nullptr;
std::vector<Dimension> dimensions;
if (!array->variable()->isArray() && array->variable()->dimensions().empty()) {
stringLiteral = array->getValueTokenMinStrSize();
if (!stringLiteral)
continue;
bool mightBeLarger;
if (array->variable()->isArray() && !array->variable()->dimensions().empty()) {
dimensions = array->variable()->dimensions();
mightBeLarger = (dimensions.size() >= 1 && (dimensions[0].num <= 1 || !dimensions[0].tok));
} else if (const Token *stringLiteral = array->getValueTokenMinStrSize()) {
Dimension dim;
dim.tok = nullptr;
dim.num = Token::getStrSize(stringLiteral);
dim.known = array->hasKnownValue();
dimensions.emplace_back(dim);
mightBeLarger = false;
} else if (array->valueType() && array->valueType()->pointer >= 1 && array->valueType()->isIntegral()) {
dimensions = getDynamicDimensions(array, array->valueType()->typeSize(*mSettings));
mightBeLarger = false;
}
const MathLib::bigint dim = stringLiteral ? Token::getStrSize(stringLiteral) : array->variable()->dimensions()[0].num;
if (dimensions.empty())
continue;
const MathLib::bigint dim = dimensions[0].num;
// Positive index
if (stringLiteral || dim > 1) { // TODO check arrays with dim 1 also
if (!mightBeLarger) { // TODO check arrays with dim 1 also
for (int cond = 0; cond < 2; cond++) {
const ValueFlow::Value *value = indexToken->getMaxValue(cond == 1);
if (!value)
@ -222,22 +257,22 @@ void CheckBufferOverrun::arrayIndex()
if (parent->isUnaryOp("&"))
continue;
}
arrayIndexError(tok, array->variable(), value);
arrayIndexError(tok, dimensions, value);
}
}
// Negative index
const ValueFlow::Value *negativeValue = indexToken->getValueLE(-1, mSettings);
if (negativeValue) {
negativeIndexError(tok, array->variable(), negativeValue);
negativeIndexError(tok, dimensions, negativeValue);
}
}
}
static std::string arrayIndexMessage(const Token *tok, const Variable *var, const ValueFlow::Value *index)
static std::string arrayIndexMessage(const Token *tok, const std::vector<Dimension> &dimensions, const ValueFlow::Value *index)
{
std::string array = tok->astOperand1()->expressionString();
for (const Dimension &dim : var->dimensions())
for (const Dimension &dim : dimensions)
array += "[" + MathLib::toString(dim.num) + "]";
std::ostringstream errmsg;
@ -250,7 +285,7 @@ static std::string arrayIndexMessage(const Token *tok, const Variable *var, cons
return errmsg.str();
}
void CheckBufferOverrun::arrayIndexError(const Token *tok, const Variable *var, const ValueFlow::Value *index)
void CheckBufferOverrun::arrayIndexError(const Token *tok, const std::vector<Dimension> &dimensions, const ValueFlow::Value *index)
{
if (!tok) {
reportError(tok, Severity::error, "arrayIndexOutOfBounds", "Array 'arr[16]' accessed at index 16, which is out of bounds.", CWE788, false);
@ -261,12 +296,12 @@ void CheckBufferOverrun::arrayIndexError(const Token *tok, const Variable *var,
reportError(getErrorPath(tok, index, "Array index out of bounds"),
index->errorSeverity() ? Severity::error : Severity::warning,
index->condition ? "arrayIndexOutOfBoundsCond" : "arrayIndexOutOfBounds",
arrayIndexMessage(tok, var, index),
arrayIndexMessage(tok, dimensions, index),
CWE788,
index->isInconclusive());
}
void CheckBufferOverrun::negativeIndexError(const Token *tok, const Variable *var, const ValueFlow::Value *negativeValue)
void CheckBufferOverrun::negativeIndexError(const Token *tok, const std::vector<Dimension> &dimensions, const ValueFlow::Value *negativeValue)
{
if (!negativeValue) {
reportError(tok, Severity::error, "negativeIndex", "Negative array index", CWE786, false);
@ -279,7 +314,7 @@ void CheckBufferOverrun::negativeIndexError(const Token *tok, const Variable *va
reportError(getErrorPath(tok, negativeValue, "Negative array index"),
negativeValue->errorSeverity() ? Severity::error : Severity::warning,
"negativeIndex",
arrayIndexMessage(tok, var, negativeValue),
arrayIndexMessage(tok, dimensions, negativeValue),
CWE786,
negativeValue->isInconclusive());
}
@ -299,30 +334,8 @@ size_t CheckBufferOverrun::getBufferSize(const Token *bufTok) const
dim *= d.num;
if (var->isPointerArray())
return dim * mSettings->sizeof_pointer;
switch (bufTok->valueType()->type) {
case ValueType::Type::BOOL:
return dim * mSettings->sizeof_bool;
case ValueType::Type::CHAR:
return dim;
case ValueType::Type::SHORT:
return dim * mSettings->sizeof_short;
case ValueType::Type::INT:
return dim * mSettings->sizeof_int;
case ValueType::Type::LONG:
return dim * mSettings->sizeof_long;
case ValueType::Type::LONGLONG:
return dim * mSettings->sizeof_long_long;
case ValueType::Type::FLOAT:
return dim * mSettings->sizeof_float;
case ValueType::Type::DOUBLE:
return dim * mSettings->sizeof_double;
case ValueType::Type::LONGDOUBLE:
return dim * mSettings->sizeof_long_double;
default:
// TODO: Get size of other types
break;
};
return 0;
const MathLib::bigint typeSize = bufTok->valueType()->typeSize(*mSettings);
return dim * typeSize;
}
// TODO: For pointers get pointer value..
return 0;

View File

@ -27,6 +27,7 @@
#include "errorlogger.h"
#include "mathlib.h"
#include "tokenize.h"
#include "symboldatabase.h"
#include <cstddef>
#include <list>
@ -69,16 +70,16 @@ public:
void getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const OVERRIDE {
CheckBufferOverrun c(nullptr, settings, errorLogger);
c.arrayIndexError(nullptr, nullptr, nullptr);
c.negativeIndexError(nullptr, nullptr, nullptr);
c.arrayIndexError(nullptr, std::vector<Dimension>(), nullptr);
c.negativeIndexError(nullptr, std::vector<Dimension>(), nullptr);
c.arrayIndexThenCheckError(nullptr, "i");
}
private:
void arrayIndex();
void arrayIndexError(const Token *tok, const Variable *var, const ValueFlow::Value *index);
void negativeIndexError(const Token *tok, const Variable *var, const ValueFlow::Value *negativeValue);
void arrayIndexError(const Token *tok, const std::vector<Dimension> &dimensions, const ValueFlow::Value *index);
void negativeIndexError(const Token *tok, const std::vector<Dimension> &dimensions, const ValueFlow::Value *negativeValue);
void bufferOverflow();
void bufferOverflowError(const Token *tok);
@ -90,7 +91,6 @@ private:
void terminateStrncpyError(const Token *tok, const std::string &varname);
void bufferNotZeroTerminatedError(const Token *tok, const std::string &varname, const std::string &function);
size_t getBufferSize(const Token *bufTok) const;
static std::string myName() {

View File

@ -5636,6 +5636,33 @@ std::string ValueType::dump() const
return ret.str();
}
MathLib::bigint ValueType::typeSize(const cppcheck::Platform &platform) const
{
switch (type) {
case ValueType::Type::BOOL:
return platform.sizeof_bool;
case ValueType::Type::CHAR:
return 1;
case ValueType::Type::SHORT:
return platform.sizeof_short;
case ValueType::Type::INT:
return platform.sizeof_int;
case ValueType::Type::LONG:
return platform.sizeof_long;
case ValueType::Type::LONGLONG:
return platform.sizeof_long_long;
case ValueType::Type::FLOAT:
return platform.sizeof_float;
case ValueType::Type::DOUBLE:
return platform.sizeof_double;
case ValueType::Type::LONGDOUBLE:
return platform.sizeof_long_double;
default:
return 0;
};
}
std::string ValueType::str() const
{
std::string ret;

View File

@ -24,6 +24,7 @@
#include "config.h"
#include "library.h"
#include "mathlib.h"
#include "platform.h"
#include "token.h"
#include <cstddef>
@ -1127,6 +1128,8 @@ public:
return typeScope && typeScope->type == Scope::eEnum;
}
MathLib::bigint typeSize(const cppcheck::Platform &platform) const;
std::string str() const;
std::string dump() const;
};

View File

@ -1432,6 +1432,9 @@ void Token::printValueFlow(bool xml, std::ostream &out) const
case ValueFlow::Value::UNINIT:
out << "uninit=\"1\"";
break;
case ValueFlow::Value::BUFFER_SIZE:
out << "buffer-size=\"" << value.intvalue << "\"";
break;
case ValueFlow::Value::CONTAINER_SIZE:
out << "container-size=\"" << value.intvalue << '\"';
break;
@ -1472,6 +1475,7 @@ void Token::printValueFlow(bool xml, std::ostream &out) const
case ValueFlow::Value::UNINIT:
out << "Uninit";
break;
case ValueFlow::Value::BUFFER_SIZE:
case ValueFlow::Value::CONTAINER_SIZE:
out << "size=" << value.intvalue;
break;

View File

@ -5006,6 +5006,55 @@ static void valueFlowFwdAnalysis(const TokenList *tokenlist, const Settings *set
}
}
static void valueFlowDynamicBufferSize(TokenList *tokenlist, SymbolDatabase *symboldatabase, ErrorLogger *errorLogger, const Settings *settings)
{
for (const Scope *functionScope : symboldatabase->functionScopes) {
for (const Token *tok = functionScope->bodyStart; tok != functionScope->bodyEnd; tok = tok->next()) {
if (!Token::Match(tok, "[;{}] %var% ="))
continue;
if (!tok->next()->variable())
continue;
const Token *rhs = tok->tokAt(2)->astOperand2();
while (rhs && rhs->isCast())
rhs = rhs->astOperand2() ? rhs->astOperand2() : rhs->astOperand1();
if (!rhs)
continue;
if (!Token::Match(rhs->previous(), "%name% ("))
continue;
const Library::AllocFunc *allocFunc = settings->library.alloc(rhs->previous());
if (!allocFunc || allocFunc->bufferSizeArgValue < 1)
continue;
const std::vector<const Token *> args = getArguments(rhs->previous());
if (args.size() < allocFunc->bufferSizeArgValue)
continue;
const Token *sizeArg = args[allocFunc->bufferSizeArgValue - 1];
if (!sizeArg || !sizeArg->hasKnownIntValue())
continue;
ValueFlow::Value value(sizeArg->getKnownIntValue());
value.valueType = ValueFlow::Value::ValueType::BUFFER_SIZE;
value.setKnown();
const std::list<ValueFlow::Value> values{value};
valueFlowForward(const_cast<Token *>(rhs),
functionScope->bodyEnd,
tok->next()->variable(),
tok->next()->varId(),
values,
true,
false,
tokenlist,
errorLogger,
settings);
}
}
}
ValueFlow::Value::Value(const Token *c, long long val)
: valueType(INT),
intvalue(val),
@ -5037,6 +5086,7 @@ std::string ValueFlow::Value::infoString() const
return "<Moved>";
case UNINIT:
return "<Uninit>";
case BUFFER_SIZE:
case CONTAINER_SIZE:
return "size=" + MathLib::toString(intvalue);
case LIFETIME:
@ -5102,6 +5152,8 @@ void ValueFlow::setValues(TokenList *tokenlist, SymbolDatabase* symboldatabase,
valueFlowContainerAfterCondition(tokenlist, symboldatabase, errorLogger, settings);
}
}
valueFlowDynamicBufferSize(tokenlist, symboldatabase, errorLogger, settings);
}

View File

@ -80,6 +80,10 @@ namespace ValueFlow {
break;
case UNINIT:
break;
case BUFFER_SIZE:
if (intvalue != rhs.intvalue)
return false;
break;
case CONTAINER_SIZE:
if (intvalue != rhs.intvalue)
return false;
@ -99,7 +103,7 @@ namespace ValueFlow {
std::string infoString() const;
enum ValueType { INT, TOK, FLOAT, MOVED, UNINIT, CONTAINER_SIZE, LIFETIME } valueType;
enum ValueType { INT, TOK, FLOAT, MOVED, UNINIT, CONTAINER_SIZE, LIFETIME, BUFFER_SIZE } valueType;
bool isIntValue() const {
return valueType == INT;
}
@ -121,6 +125,9 @@ namespace ValueFlow {
bool isLifetimeValue() const {
return valueType == LIFETIME;
}
bool isBufferSizeValue() const {
return valueType == BUFFER_SIZE;
}
bool isLocalLifetimeValue() const {
return valueType == LIFETIME && lifetimeScope == Local;

View File

@ -121,7 +121,7 @@ private:
TEST_CASE(array_index_39);
TEST_CASE(array_index_40); // loop variable calculation, taking address
TEST_CASE(array_index_41); // structs with the same name
// TODO malloc TEST_CASE(array_index_42);
TEST_CASE(array_index_42);
TEST_CASE(array_index_43); // struct with array
TEST_CASE(array_index_44); // #3979
TEST_CASE(array_index_45); // #4207 - calling function with variable number of parameters (...)
@ -202,9 +202,9 @@ private:
TEST_CASE(assign1);
// TODO TEST_CASE(alloc_new); // Buffer allocated with new
// TODO TEST_CASE(alloc_malloc); // Buffer allocated with malloc
// TODO TEST_CASE(alloc_string); // statically allocated buffer
// TODO new TEST_CASE(alloc_new); // Buffer allocated with new
TEST_CASE(alloc_malloc); // Buffer allocated with malloc
TEST_CASE(alloc_string); // statically allocated buffer
// TODO TEST_CASE(alloc_alloca); // Buffer allocated with alloca
// TODO TEST_CASE(countSprintfLength);