2009-01-31 20:29:27 +01:00
|
|
|
/*
|
|
|
|
* Cppcheck - A tool for static C/C++ code analysis
|
2019-02-09 07:24:06 +01:00
|
|
|
* Copyright (C) 2007-2019 Cppcheck team.
|
2009-01-31 20:29:27 +01:00
|
|
|
*
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
2009-09-27 17:08:31 +02:00
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
2009-01-31 20:29:27 +01:00
|
|
|
*/
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
// Buffer overrun..
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
#include "checkbufferoverrun.h"
|
|
|
|
|
2017-05-27 04:33:47 +02:00
|
|
|
#include "astutils.h"
|
|
|
|
#include "library.h"
|
2009-08-02 14:26:26 +02:00
|
|
|
#include "mathlib.h"
|
2017-05-27 04:33:47 +02:00
|
|
|
#include "settings.h"
|
2011-06-23 04:44:11 +02:00
|
|
|
#include "symboldatabase.h"
|
2017-05-27 04:33:47 +02:00
|
|
|
#include "token.h"
|
|
|
|
#include "tokenize.h"
|
|
|
|
#include "tokenlist.h"
|
2017-05-23 14:58:43 +02:00
|
|
|
#include "utils.h"
|
2017-05-27 04:33:47 +02:00
|
|
|
#include "valueflow.h"
|
2009-02-11 06:16:10 +01:00
|
|
|
|
2017-05-27 04:33:47 +02:00
|
|
|
#include <tinyxml2.h>
|
2009-01-31 20:29:27 +01:00
|
|
|
#include <algorithm>
|
2011-12-23 22:31:48 +01:00
|
|
|
#include <cstdlib>
|
2017-05-27 04:33:47 +02:00
|
|
|
#include <sstream>
|
2015-07-04 09:42:42 +02:00
|
|
|
#include <stack>
|
2017-05-27 04:33:47 +02:00
|
|
|
#include <utility>
|
2009-01-31 20:29:27 +01:00
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2009-03-20 18:16:21 +01:00
|
|
|
// Register this check class (by creating a static instance of it)
|
2011-10-13 20:53:06 +02:00
|
|
|
namespace {
|
|
|
|
CheckBufferOverrun instance;
|
2009-03-20 18:16:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
2009-01-31 20:29:27 +01:00
|
|
|
|
2016-01-25 20:01:48 +01:00
|
|
|
// CWE ids used:
|
2016-08-15 18:04:55 +02:00
|
|
|
static const CWE CWE131(131U); // Incorrect Calculation of Buffer Size
|
|
|
|
static const CWE CWE170(170U); // Improper Null Termination
|
|
|
|
static const CWE CWE398(398U); // Indicator of Poor Code Quality
|
|
|
|
static const CWE CWE682(682U); // Incorrect Calculation
|
|
|
|
static const CWE CWE758(758U); // Reliance on Undefined, Unspecified, or Implementation-Defined Behavior
|
|
|
|
static const CWE CWE786(786U); // Access of Memory Location Before Start of Buffer
|
|
|
|
static const CWE CWE788(788U); // Access of Memory Location After End of Buffer
|
2016-01-25 20:01:48 +01:00
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2019-03-09 22:14:02 +01:00
|
|
|
static size_t getMinFormatStringOutputLength(const std::vector<const Token*> ¶meters, unsigned int formatStringArgNr)
|
2009-09-25 18:23:44 +02:00
|
|
|
{
|
2019-03-09 22:14:02 +01:00
|
|
|
if (formatStringArgNr == 0 || formatStringArgNr > parameters.size())
|
|
|
|
return 0;
|
|
|
|
if (parameters[formatStringArgNr - 1]->tokType() != Token::eString)
|
|
|
|
return 0;
|
|
|
|
const std::string &formatString = parameters[formatStringArgNr - 1]->str();
|
2009-10-18 12:58:48 +02:00
|
|
|
bool percentCharFound = false;
|
2019-03-09 22:14:02 +01:00
|
|
|
std::size_t outputStringSize = 0;
|
2009-10-18 12:58:48 +02:00
|
|
|
bool handleNextParameter = false;
|
2015-02-18 20:56:44 +01:00
|
|
|
std::string digits_string;
|
2009-10-18 12:58:48 +02:00
|
|
|
bool i_d_x_f_found = false;
|
2010-12-31 09:30:56 +01:00
|
|
|
std::size_t parameterLength = 0;
|
2019-03-09 22:14:02 +01:00
|
|
|
unsigned int inputArgNr = formatStringArgNr;
|
|
|
|
for (std::string::size_type i = 1; i + 1 < formatString.length(); ++i) {
|
|
|
|
if (formatString[i] == '\\') {
|
|
|
|
if (i < formatString.length() - 1 && formatString[i + 1] == '0')
|
2009-10-18 12:58:48 +02:00
|
|
|
break;
|
|
|
|
|
2019-03-09 22:14:02 +01:00
|
|
|
++outputStringSize;
|
2009-10-18 12:58:48 +02:00
|
|
|
++i;
|
|
|
|
continue;
|
2009-09-25 18:23:44 +02:00
|
|
|
}
|
2009-10-18 12:58:48 +02:00
|
|
|
|
2011-10-13 20:53:06 +02:00
|
|
|
if (percentCharFound) {
|
2019-03-09 22:14:02 +01:00
|
|
|
switch (formatString[i]) {
|
2009-10-18 12:58:48 +02:00
|
|
|
case 'f':
|
|
|
|
case 'x':
|
|
|
|
case 'X':
|
|
|
|
case 'i':
|
|
|
|
i_d_x_f_found = true;
|
2015-06-28 18:07:31 +02:00
|
|
|
handleNextParameter = true;
|
2019-03-09 22:14:02 +01:00
|
|
|
parameterLength = 1; // TODO
|
2015-06-28 18:07:31 +02:00
|
|
|
break;
|
2009-10-18 12:58:48 +02:00
|
|
|
case 'c':
|
|
|
|
case 'e':
|
|
|
|
case 'E':
|
|
|
|
case 'g':
|
|
|
|
case 'o':
|
|
|
|
case 'u':
|
|
|
|
case 'p':
|
|
|
|
case 'n':
|
2009-10-19 23:48:29 +02:00
|
|
|
handleNextParameter = true;
|
2019-03-09 22:14:02 +01:00
|
|
|
parameterLength = 1; // TODO
|
2009-10-19 23:48:29 +02:00
|
|
|
break;
|
|
|
|
case 'd':
|
|
|
|
i_d_x_f_found = true;
|
2019-03-09 22:14:02 +01:00
|
|
|
parameterLength = 1;
|
|
|
|
if (inputArgNr < parameters.size() && parameters[inputArgNr]->hasKnownIntValue())
|
|
|
|
parameterLength = MathLib::toString(parameters[inputArgNr]->getKnownIntValue()).length();
|
2009-10-19 23:48:29 +02:00
|
|
|
|
2009-10-18 12:58:48 +02:00
|
|
|
handleNextParameter = true;
|
|
|
|
break;
|
|
|
|
case 's':
|
2019-03-09 22:14:02 +01:00
|
|
|
parameterLength = 0;
|
|
|
|
if (inputArgNr < parameters.size() && parameters[inputArgNr]->tokType() == Token::eString)
|
|
|
|
parameterLength = Token::getStrLength(parameters[inputArgNr]);
|
2009-10-08 15:27:46 +02:00
|
|
|
|
2009-10-18 12:58:48 +02:00
|
|
|
handleNextParameter = true;
|
|
|
|
break;
|
2009-10-08 15:27:46 +02:00
|
|
|
}
|
2009-10-18 12:58:48 +02:00
|
|
|
}
|
2009-10-08 15:27:46 +02:00
|
|
|
|
2019-03-09 22:14:02 +01:00
|
|
|
if (formatString[i] == '%')
|
2009-10-18 12:58:48 +02:00
|
|
|
percentCharFound = !percentCharFound;
|
2011-10-13 20:53:06 +02:00
|
|
|
else if (percentCharFound) {
|
2019-03-09 22:14:02 +01:00
|
|
|
digits_string.append(1, formatString[i]);
|
2009-09-25 18:23:44 +02:00
|
|
|
}
|
|
|
|
|
2010-04-02 07:30:58 +02:00
|
|
|
if (!percentCharFound)
|
2019-03-09 22:14:02 +01:00
|
|
|
outputStringSize++;
|
2009-09-25 18:23:44 +02:00
|
|
|
|
2011-10-13 20:53:06 +02:00
|
|
|
if (handleNextParameter) {
|
2010-08-06 21:02:43 +02:00
|
|
|
unsigned int tempDigits = static_cast<unsigned int>(std::abs(std::atoi(digits_string.c_str())));
|
2010-04-02 07:30:58 +02:00
|
|
|
if (i_d_x_f_found)
|
2010-08-06 21:02:43 +02:00
|
|
|
tempDigits = std::max(static_cast<unsigned int>(tempDigits), 1U);
|
2009-10-08 15:27:46 +02:00
|
|
|
|
2011-10-13 20:53:06 +02:00
|
|
|
if (digits_string.find('.') != std::string::npos) {
|
2009-10-11 21:07:18 +02:00
|
|
|
const std::string endStr = digits_string.substr(digits_string.find('.') + 1);
|
2014-07-07 21:25:30 +02:00
|
|
|
const unsigned int maxLen = std::max(static_cast<unsigned int>(std::abs(std::atoi(endStr.c_str()))), 1U);
|
2009-10-11 21:07:18 +02:00
|
|
|
|
2019-03-09 22:14:02 +01:00
|
|
|
if (formatString[i] == 's') {
|
2009-10-19 23:48:29 +02:00
|
|
|
// For strings, the length after the dot "%.2s" will limit
|
|
|
|
// the length of the string.
|
2010-04-02 07:30:58 +02:00
|
|
|
if (parameterLength > maxLen)
|
2009-10-19 23:48:29 +02:00
|
|
|
parameterLength = maxLen;
|
2011-10-13 20:53:06 +02:00
|
|
|
} else {
|
2009-10-19 23:48:29 +02:00
|
|
|
// For integers, the length after the dot "%.2d" can
|
|
|
|
// increase required length
|
2010-04-02 07:30:58 +02:00
|
|
|
if (tempDigits < maxLen)
|
2009-10-19 23:48:29 +02:00
|
|
|
tempDigits = maxLen;
|
|
|
|
}
|
2009-10-11 21:07:18 +02:00
|
|
|
}
|
|
|
|
|
2010-04-02 07:30:58 +02:00
|
|
|
if (tempDigits < parameterLength)
|
2019-03-09 22:14:02 +01:00
|
|
|
outputStringSize += parameterLength;
|
2009-10-08 15:27:46 +02:00
|
|
|
else
|
2019-03-09 22:14:02 +01:00
|
|
|
outputStringSize += tempDigits;
|
2009-10-08 15:27:46 +02:00
|
|
|
|
|
|
|
parameterLength = 0;
|
2017-06-02 20:38:00 +02:00
|
|
|
digits_string.clear();
|
2009-10-18 12:58:48 +02:00
|
|
|
i_d_x_f_found = false;
|
|
|
|
percentCharFound = false;
|
|
|
|
handleNextParameter = false;
|
2019-03-09 22:14:02 +01:00
|
|
|
++inputArgNr;
|
2009-09-25 18:23:44 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-09 22:14:02 +01:00
|
|
|
return outputStringSize;
|
2009-09-25 18:23:44 +02:00
|
|
|
}
|
|
|
|
|
2010-05-26 10:56:34 +02:00
|
|
|
//---------------------------------------------------------------------------
|
2019-03-09 22:14:02 +01:00
|
|
|
|
|
|
|
void CheckBufferOverrun::arrayIndex()
|
2010-05-26 10:56:34 +02:00
|
|
|
{
|
2019-03-09 22:14:02 +01:00
|
|
|
for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) {
|
2019-03-12 06:46:38 +01:00
|
|
|
if (tok->str() != "[")
|
2019-03-09 22:14:02 +01:00
|
|
|
continue;
|
2019-03-12 06:46:38 +01:00
|
|
|
const Token *array = tok->astOperand1();
|
|
|
|
while (Token::Match(array, ".|::"))
|
|
|
|
array = array->astOperand2();
|
|
|
|
if (!array|| !array->variable() || array->variable()->nameToken() == array)
|
|
|
|
continue;
|
|
|
|
if (!array->scope()->isExecutable()) {
|
2019-03-09 22:14:02 +01:00
|
|
|
// LHS in non-executable scope => This is just a definition
|
2019-03-12 06:46:38 +01:00
|
|
|
const Token *parent = tok;
|
2019-03-09 22:14:02 +01:00
|
|
|
while (parent && !Token::simpleMatch(parent->astParent(), "="))
|
|
|
|
parent = parent->astParent();
|
2019-03-13 06:39:09 +01:00
|
|
|
if (!parent || parent == parent->astParent()->astOperand1())
|
2017-03-27 11:48:34 +02:00
|
|
|
continue;
|
2019-03-09 22:14:02 +01:00
|
|
|
}
|
2019-03-12 06:46:38 +01:00
|
|
|
|
|
|
|
const Token *indexToken = tok->astOperand2();
|
2019-03-09 22:14:02 +01:00
|
|
|
if (!indexToken)
|
|
|
|
continue;
|
2017-03-27 11:48:34 +02:00
|
|
|
|
2019-03-09 22:14:02 +01:00
|
|
|
const Token *stringLiteral = nullptr;
|
2017-03-27 11:48:34 +02:00
|
|
|
|
2019-03-12 06:46:38 +01:00
|
|
|
if (!array->variable()->isArray() && array->variable()->dimensions().empty()) {
|
|
|
|
stringLiteral = array->getValueTokenMinStrSize();
|
2019-03-09 22:14:02 +01:00
|
|
|
if (!stringLiteral)
|
2012-10-13 11:16:48 +02:00
|
|
|
continue;
|
2010-05-26 10:56:34 +02:00
|
|
|
}
|
2010-06-02 07:41:07 +02:00
|
|
|
|
2019-03-12 06:46:38 +01:00
|
|
|
const MathLib::bigint dim = stringLiteral ? Token::getStrSize(stringLiteral) : array->variable()->dimensions()[0].num;
|
2014-07-06 08:41:39 +02:00
|
|
|
|
2019-03-09 22:14:02 +01:00
|
|
|
// Positive index
|
|
|
|
if (stringLiteral || dim > 1) { // TODO check arrays with dim 1 also
|
|
|
|
for (int cond = 0; cond < 2; cond++) {
|
|
|
|
const ValueFlow::Value *value = indexToken->getMaxValue(cond == 1);
|
|
|
|
if (!value)
|
2015-07-27 16:39:41 +02:00
|
|
|
continue;
|
2019-03-09 22:14:02 +01:00
|
|
|
const MathLib::bigint index = value->intvalue;
|
|
|
|
if (index < dim)
|
2014-07-06 08:41:39 +02:00
|
|
|
continue;
|
2019-03-09 22:14:02 +01:00
|
|
|
if (index == dim) {
|
2019-03-12 06:46:38 +01:00
|
|
|
const Token *parent = tok;
|
2019-03-09 22:14:02 +01:00
|
|
|
while (Token::simpleMatch(parent, "["))
|
|
|
|
parent = parent->astParent();
|
2019-03-12 06:46:38 +01:00
|
|
|
if (parent->isUnaryOp("&"))
|
2019-03-09 22:14:02 +01:00
|
|
|
continue;
|
|
|
|
}
|
2019-03-12 06:46:38 +01:00
|
|
|
arrayIndexError(tok, array->variable(), value);
|
2014-07-06 08:41:39 +02:00
|
|
|
}
|
|
|
|
}
|
2010-06-02 07:41:07 +02:00
|
|
|
|
2019-03-09 22:14:02 +01:00
|
|
|
// Negative index
|
|
|
|
const ValueFlow::Value *negativeValue = indexToken->getValueLE(-1, mSettings);
|
|
|
|
if (negativeValue) {
|
2019-03-12 06:46:38 +01:00
|
|
|
negativeIndexError(tok, array->variable(), negativeValue);
|
2011-12-09 22:28:10 +01:00
|
|
|
}
|
2010-06-02 07:41:07 +02:00
|
|
|
}
|
|
|
|
}
|
2010-05-26 10:56:34 +02:00
|
|
|
|
2019-03-11 15:32:30 +01:00
|
|
|
static std::string arrayIndexMessage(const Token *tok, const Variable *var, const ValueFlow::Value *index)
|
|
|
|
{
|
2019-03-09 22:14:02 +01:00
|
|
|
std::string array = tok->astOperand1()->expressionString();
|
|
|
|
for (const Dimension &dim : var->dimensions())
|
|
|
|
array += "[" + MathLib::toString(dim.num) + "]";
|
2010-05-26 10:56:34 +02:00
|
|
|
|
2017-05-16 19:08:47 +02:00
|
|
|
std::ostringstream errmsg;
|
2019-03-09 22:14:02 +01:00
|
|
|
if (index->condition)
|
|
|
|
errmsg << ValueFlow::eitherTheConditionIsRedundant(index->condition)
|
|
|
|
<< " or the array '" + array + "' is accessed at index " << index->intvalue << ", which is out of bounds.";
|
2017-05-16 19:08:47 +02:00
|
|
|
else
|
2019-03-09 22:14:02 +01:00
|
|
|
errmsg << "Array '" << array << "' accessed at index " << index->intvalue << ", which is out of bounds.";
|
2014-04-02 06:49:28 +02:00
|
|
|
|
2019-03-09 22:14:02 +01:00
|
|
|
return errmsg.str();
|
2010-04-18 11:08:29 +02:00
|
|
|
}
|
|
|
|
|
2019-03-09 22:14:02 +01:00
|
|
|
void CheckBufferOverrun::arrayIndexError(const Token *tok, const Variable *var, const ValueFlow::Value *index)
|
2011-06-23 04:44:11 +02:00
|
|
|
{
|
2019-03-09 22:14:02 +01:00
|
|
|
if (!tok) {
|
|
|
|
reportError(tok, Severity::error, "arrayIndexOutOfBounds", "Array 'arr[16]' accessed at index 16, which is out of bounds.", CWE788, false);
|
2019-03-11 19:12:03 +01:00
|
|
|
reportError(tok, Severity::warning, "arrayIndexOutOfBoundsCond", "Array 'arr[16]' accessed at index 16, which is out of bounds.", CWE788, false);
|
2019-03-09 22:14:02 +01:00
|
|
|
return;
|
2015-01-30 20:27:48 +01:00
|
|
|
}
|
2011-06-23 04:44:11 +02:00
|
|
|
|
2019-03-09 22:14:02 +01:00
|
|
|
reportError(getErrorPath(tok, index, "Array index out of bounds"),
|
|
|
|
index->errorSeverity() ? Severity::error : Severity::warning,
|
2019-03-11 21:39:39 +01:00
|
|
|
index->condition ? "arrayIndexOutOfBoundsCond" : "arrayIndexOutOfBounds",
|
2019-03-09 22:14:02 +01:00
|
|
|
arrayIndexMessage(tok, var, index),
|
|
|
|
CWE788,
|
|
|
|
index->isInconclusive());
|
2015-11-08 12:39:08 +01:00
|
|
|
}
|
|
|
|
|
2019-03-09 22:14:02 +01:00
|
|
|
void CheckBufferOverrun::negativeIndexError(const Token *tok, const Variable *var, const ValueFlow::Value *negativeValue)
|
2015-11-08 12:39:08 +01:00
|
|
|
{
|
2019-03-09 22:14:02 +01:00
|
|
|
if (!negativeValue) {
|
|
|
|
reportError(tok, Severity::error, "negativeIndex", "Negative array index", CWE786, false);
|
|
|
|
return;
|
2015-11-08 12:39:08 +01:00
|
|
|
}
|
2011-08-04 11:15:14 +02:00
|
|
|
|
2019-03-09 22:14:02 +01:00
|
|
|
if (!negativeValue->errorSeverity() && !mSettings->isEnabled(Settings::WARNING))
|
2011-08-04 11:15:14 +02:00
|
|
|
return;
|
|
|
|
|
2019-03-09 22:14:02 +01:00
|
|
|
reportError(getErrorPath(tok, negativeValue, "Negative array index"),
|
|
|
|
negativeValue->errorSeverity() ? Severity::error : Severity::warning,
|
|
|
|
"negativeIndex",
|
|
|
|
arrayIndexMessage(tok, var, negativeValue),
|
|
|
|
CWE786,
|
|
|
|
negativeValue->isInconclusive());
|
2011-08-04 11:15:14 +02:00
|
|
|
}
|
|
|
|
|
2019-03-11 19:20:06 +01:00
|
|
|
//---------------------------------------------------------------------------
|
2013-03-19 08:22:48 +01:00
|
|
|
|
2019-03-09 22:14:02 +01:00
|
|
|
size_t CheckBufferOverrun::getBufferSize(const Token *bufTok) const
|
2016-10-29 12:18:11 +02:00
|
|
|
{
|
2019-03-09 22:14:02 +01:00
|
|
|
if (!bufTok->valueType())
|
|
|
|
return 0;
|
|
|
|
const Variable *var = bufTok->variable();
|
|
|
|
if (!var)
|
|
|
|
return 0;
|
|
|
|
if (!var->dimensions().empty()) {
|
|
|
|
MathLib::bigint dim = 1;
|
|
|
|
for (const Dimension &d : var->dimensions())
|
|
|
|
dim *= d.num;
|
2019-03-11 19:40:17 +01:00
|
|
|
if (var->isPointerArray())
|
|
|
|
return dim * mSettings->sizeof_pointer;
|
2019-03-09 22:14:02 +01:00
|
|
|
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;
|
2016-10-29 12:18:11 +02:00
|
|
|
}
|
2019-03-09 22:14:02 +01:00
|
|
|
// TODO: For pointers get pointer value..
|
|
|
|
return 0;
|
2016-10-29 12:18:11 +02:00
|
|
|
}
|
2019-03-11 19:20:06 +01:00
|
|
|
//---------------------------------------------------------------------------
|
2016-10-29 12:18:11 +02:00
|
|
|
|
2019-03-09 22:14:02 +01:00
|
|
|
static bool checkBufferSize(const Token *ftok, const Library::ArgumentChecks::MinSize &minsize, const std::vector<const Token *> &args, const MathLib::bigint bufferSize, const Settings *settings)
|
2014-11-15 10:43:49 +01:00
|
|
|
{
|
2019-03-09 22:14:02 +01:00
|
|
|
const Token * const arg = (minsize.arg > 0 && minsize.arg - 1 < args.size()) ? args[minsize.arg - 1] : nullptr;
|
|
|
|
const Token * const arg2 = (minsize.arg2 > 0 && minsize.arg2 - 1 < args.size()) ? args[minsize.arg2 - 1] : nullptr;
|
2014-12-02 06:41:18 +01:00
|
|
|
|
2019-03-09 22:14:02 +01:00
|
|
|
switch (minsize.type) {
|
|
|
|
case Library::ArgumentChecks::MinSize::Type::STRLEN:
|
|
|
|
if (settings->library.isargformatstr(ftok, minsize.arg)) {
|
|
|
|
return getMinFormatStringOutputLength(args, minsize.arg) < bufferSize;
|
|
|
|
} else if (arg) {
|
|
|
|
const Token *strtoken = arg->getValueTokenMaxStrLength();
|
|
|
|
if (strtoken)
|
|
|
|
return Token::getStrLength(strtoken) < bufferSize;
|
2014-11-15 10:43:49 +01:00
|
|
|
}
|
2019-03-09 22:14:02 +01:00
|
|
|
break;
|
|
|
|
case Library::ArgumentChecks::MinSize::Type::ARGVALUE:
|
|
|
|
if (arg && arg->hasKnownIntValue())
|
|
|
|
return arg->getKnownIntValue() <= bufferSize;
|
|
|
|
break;
|
|
|
|
case Library::ArgumentChecks::MinSize::Type::SIZEOF:
|
|
|
|
// TODO
|
|
|
|
break;
|
|
|
|
case Library::ArgumentChecks::MinSize::Type::MUL:
|
|
|
|
if (arg && arg2 && arg->hasKnownIntValue() && arg2->hasKnownIntValue())
|
|
|
|
return (arg->getKnownIntValue() * arg2->getKnownIntValue()) <= bufferSize;
|
|
|
|
break;
|
|
|
|
case Library::ArgumentChecks::MinSize::Type::NONE:
|
|
|
|
break;
|
|
|
|
};
|
|
|
|
return true;
|
2014-11-15 10:43:49 +01:00
|
|
|
}
|
|
|
|
|
2017-03-30 10:14:17 +02:00
|
|
|
|
2019-03-09 22:14:02 +01:00
|
|
|
void CheckBufferOverrun::bufferOverflow()
|
|
|
|
{
|
|
|
|
const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();
|
|
|
|
for (const Scope * scope : symbolDatabase->functionScopes) {
|
|
|
|
for (const Token *tok = scope->bodyStart; tok != scope->bodyEnd; tok = tok->next()) {
|
|
|
|
if (!Token::Match(tok, "%name% (") || Token::simpleMatch(tok, ") {"))
|
2017-03-30 10:14:17 +02:00
|
|
|
continue;
|
2019-03-09 22:14:02 +01:00
|
|
|
if (!mSettings->library.hasminsize(tok))
|
2017-03-30 10:14:17 +02:00
|
|
|
continue;
|
2019-03-09 22:14:02 +01:00
|
|
|
const std::vector<const Token *> args = getArguments(tok);
|
|
|
|
for (unsigned int argnr = 0; argnr < args.size(); ++argnr) {
|
|
|
|
if (!args[argnr]->valueType() || args[argnr]->valueType()->pointer == 0)
|
|
|
|
continue;
|
|
|
|
const std::vector<Library::ArgumentChecks::MinSize> *minsizes = mSettings->library.argminsizes(tok, argnr + 1);
|
|
|
|
if (!minsizes || minsizes->empty())
|
|
|
|
continue;
|
|
|
|
// Get buffer size..
|
|
|
|
const Token *argtok = args[argnr];
|
|
|
|
while (argtok && argtok->isCast())
|
|
|
|
argtok = argtok->astOperand2() ? argtok->astOperand2() : argtok->astOperand1();
|
|
|
|
while (Token::Match(argtok, ".|::"))
|
|
|
|
argtok = argtok->astOperand2();
|
|
|
|
if (!argtok || !argtok->variable())
|
|
|
|
continue;
|
|
|
|
// TODO: strcpy(buf+10, "hello");
|
|
|
|
const size_t bufferSize = getBufferSize(argtok);
|
|
|
|
if (bufferSize <= 1)
|
|
|
|
continue;
|
|
|
|
bool error = true;
|
|
|
|
for (const Library::ArgumentChecks::MinSize &minsize : *minsizes) {
|
|
|
|
if (checkBufferSize(tok, minsize, args, bufferSize, mSettings)) {
|
|
|
|
error = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (error)
|
|
|
|
bufferOverflowError(args[argnr]);
|
|
|
|
}
|
2014-11-15 10:43:49 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-07-08 20:53:08 +02:00
|
|
|
|
2019-03-09 22:14:02 +01:00
|
|
|
void CheckBufferOverrun::bufferOverflowError(const Token *tok)
|
2016-07-08 20:53:08 +02:00
|
|
|
{
|
2019-03-09 22:14:02 +01:00
|
|
|
reportError(tok, Severity::error, "bufferAccessOutOfBounds", "Buffer is accessed out of bounds: " + (tok ? tok->expressionString() : "buf"), CWE788, false);
|
2016-07-08 20:53:08 +02:00
|
|
|
}
|
2019-03-11 19:20:06 +01:00
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
void CheckBufferOverrun::arrayIndexThenCheck()
|
|
|
|
{
|
|
|
|
if (!mSettings->isEnabled(Settings::PORTABILITY))
|
|
|
|
return;
|
|
|
|
|
|
|
|
const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();
|
|
|
|
for (const Scope * const scope : symbolDatabase->functionScopes) {
|
|
|
|
for (const Token *tok = scope->bodyStart; tok && tok != scope->bodyEnd; tok = tok->next()) {
|
|
|
|
if (Token::simpleMatch(tok, "sizeof (")) {
|
|
|
|
tok = tok->linkAt(1);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Token::Match(tok, "%name% [ %var% ]")) {
|
|
|
|
tok = tok->next();
|
|
|
|
|
|
|
|
const unsigned int indexID = tok->next()->varId();
|
|
|
|
const std::string& indexName(tok->strAt(1));
|
|
|
|
|
|
|
|
// Iterate AST upwards
|
|
|
|
const Token* tok2 = tok;
|
|
|
|
const Token* tok3 = tok2;
|
|
|
|
while (tok2->astParent() && tok2->tokType() != Token::eLogicalOp) {
|
|
|
|
tok3 = tok2;
|
|
|
|
tok2 = tok2->astParent();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure that we ended at a logical operator and that we came from its left side
|
|
|
|
if (tok2->tokType() != Token::eLogicalOp || tok2->astOperand1() != tok3)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// check if array index is ok
|
|
|
|
// statement can be closed in parentheses, so "(| " is using
|
|
|
|
if (Token::Match(tok2, "&& (| %varid% <|<=", indexID))
|
|
|
|
arrayIndexThenCheckError(tok, indexName);
|
|
|
|
else if (Token::Match(tok2, "&& (| %any% >|>= %varid% !!+", indexID))
|
|
|
|
arrayIndexThenCheckError(tok, indexName);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CheckBufferOverrun::arrayIndexThenCheckError(const Token *tok, const std::string &indexName)
|
|
|
|
{
|
|
|
|
reportError(tok, Severity::style, "arrayIndexThenCheck",
|
|
|
|
"$symbol:" + indexName + "\n"
|
|
|
|
"Array index '$symbol' is used before limits check.\n"
|
|
|
|
"Defensive programming: The variable '$symbol' is used as an array index before it "
|
|
|
|
"is checked that is within limits. This can mean that the array might be accessed out of bounds. "
|
|
|
|
"Reorder conditions such as '(a[i] && i < 10)' to '(i < 10 && a[i])'. That way the array will "
|
|
|
|
"not be accessed if the index is out of limits.", CWE398, false);
|
|
|
|
}
|
2019-03-12 21:15:26 +01:00
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
void CheckBufferOverrun::stringNotZeroTerminated()
|
|
|
|
{
|
|
|
|
// this is currently 'inconclusive'. See TestBufferOverrun::terminateStrncpy3
|
|
|
|
if (!mSettings->isEnabled(Settings::WARNING) || !mSettings->inconclusive)
|
|
|
|
return;
|
|
|
|
const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();
|
|
|
|
for (const Scope * const scope : symbolDatabase->functionScopes) {
|
|
|
|
for (const Token *tok = scope->bodyStart; tok && tok != scope->bodyEnd; tok = tok->next()) {
|
2019-03-13 06:45:01 +01:00
|
|
|
if (!Token::simpleMatch(tok, "strncpy ("))
|
2019-03-12 21:15:26 +01:00
|
|
|
continue;
|
|
|
|
const std::vector<const Token *> args = getArguments(tok);
|
|
|
|
if (args.size() != 3)
|
|
|
|
continue;
|
|
|
|
const Token *sizeToken = args[2];
|
|
|
|
if (!sizeToken->hasKnownIntValue())
|
|
|
|
continue;
|
|
|
|
const size_t bufferSize = getBufferSize(args[0]);
|
|
|
|
if (bufferSize == 0 || sizeToken->getKnownIntValue() < bufferSize)
|
|
|
|
continue;
|
|
|
|
const Token *srcValue = args[1]->getValueTokenMaxStrLength();
|
|
|
|
if (srcValue && Token::getStrLength(srcValue) < sizeToken->getKnownIntValue())
|
|
|
|
continue;
|
|
|
|
// Is the buffer zero terminated after the call?
|
|
|
|
bool isZeroTerminated = false;
|
|
|
|
for (const Token *tok2 = tok->next()->link(); tok2 != scope->bodyEnd; tok2 = tok2->next()) {
|
|
|
|
if (!Token::Match(tok2, "] ="))
|
|
|
|
continue;
|
|
|
|
const Token *rhs = tok2->next()->astOperand2();
|
|
|
|
if (!rhs || !rhs->hasKnownIntValue() || rhs->getKnownIntValue() != 0)
|
|
|
|
continue;
|
|
|
|
if (isSameExpression(mTokenizer->isCPP(), false, args[0], tok2->link()->astOperand1(), mSettings->library, false, false))
|
|
|
|
isZeroTerminated = true;
|
|
|
|
}
|
|
|
|
if (isZeroTerminated)
|
|
|
|
continue;
|
|
|
|
// TODO: Locate unsafe string usage..
|
2019-03-13 06:45:01 +01:00
|
|
|
terminateStrncpyError(tok, args[0]->expressionString());
|
2019-03-12 21:15:26 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CheckBufferOverrun::terminateStrncpyError(const Token *tok, const std::string &varname)
|
|
|
|
{
|
|
|
|
const std::string shortMessage = "The buffer '$symbol' may not be null-terminated after the call to strncpy().";
|
|
|
|
reportError(tok, Severity::warning, "terminateStrncpy",
|
|
|
|
"$symbol:" + varname + '\n' +
|
|
|
|
shortMessage + '\n' +
|
|
|
|
shortMessage + ' ' +
|
|
|
|
"If the source string's size fits or exceeds the given size, strncpy() does not add a "
|
|
|
|
"zero at the end of the buffer. This causes bugs later in the code if the code "
|
|
|
|
"assumes buffer is null-terminated.", CWE170, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CheckBufferOverrun::bufferNotZeroTerminatedError(const Token *tok, const std::string &varname, const std::string &function)
|
|
|
|
{
|
|
|
|
const std::string errmsg = "$symbol:" + varname + '\n' +
|
|
|
|
"$symbol:" + function + '\n' +
|
|
|
|
"The buffer '" + varname + "' is not null-terminated after the call to " + function + "().\n"
|
|
|
|
"The buffer '" + varname + "' is not null-terminated after the call to " + function + "(). "
|
|
|
|
"This will cause bugs later in the code if the code assumes the buffer is null-terminated.";
|
|
|
|
|
|
|
|
reportError(tok, Severity::warning, "bufferNotZeroTerminated", errmsg, CWE170, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
|