/* * Cppcheck - A tool for static C/C++ code analysis * Copyright (C) 2007-2023 Cppcheck team. * * 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 * along with this program. If not, see . */ //--------------------------------------------------------------------------- // Buffer overrun.. //--------------------------------------------------------------------------- #include "checkbufferoverrun.h" #include "astutils.h" #include "errorlogger.h" #include "library.h" #include "mathlib.h" #include "platform.h" #include "settings.h" #include "symboldatabase.h" #include "token.h" #include "tokenize.h" #include "utils.h" #include "valueflow.h" #include #include #include #include #include // std::accumulate #include #include "xml.h" //--------------------------------------------------------------------------- // Register this check class (by creating a static instance of it) namespace { CheckBufferOverrun instance; } //--------------------------------------------------------------------------- // CWE ids used: static const CWE CWE131(131U); // Incorrect Calculation of Buffer Size static const CWE CWE170(170U); // Improper Null Termination static const CWE CWE_ARGUMENT_SIZE(398U); // Indicator of Poor Code Quality static const CWE CWE_ARRAY_INDEX_THEN_CHECK(398U); // Indicator of Poor Code Quality static const CWE CWE758(758U); // Reliance on Undefined, Unspecified, or Implementation-Defined Behavior static const CWE CWE_POINTER_ARITHMETIC_OVERFLOW(758U); // Reliance on Undefined, Unspecified, or Implementation-Defined Behavior static const CWE CWE_BUFFER_UNDERRUN(786U); // Access of Memory Location Before Start of Buffer static const CWE CWE_BUFFER_OVERRUN(788U); // Access of Memory Location After End of Buffer //--------------------------------------------------------------------------- static const ValueFlow::Value *getBufferSizeValue(const Token *tok) { const std::list &tokenValues = tok->values(); const auto it = std::find_if(tokenValues.cbegin(), tokenValues.cend(), std::mem_fn(&ValueFlow::Value::isBufferSizeValue)); return it == tokenValues.cend() ? nullptr : &*it; } static int getMinFormatStringOutputLength(const std::vector ¶meters, nonneg int formatStringArgNr) { 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(); bool percentCharFound = false; int outputStringSize = 0; bool handleNextParameter = false; std::string digits_string; bool i_d_x_f_found = false; int parameterLength = 0; int inputArgNr = formatStringArgNr; for (int i = 1; i + 1 < formatString.length(); ++i) { if (formatString[i] == '\\') { if (i < formatString.length() - 1 && formatString[i + 1] == '0') break; ++outputStringSize; ++i; continue; } if (percentCharFound) { switch (formatString[i]) { case 'f': case 'x': case 'X': case 'i': i_d_x_f_found = true; handleNextParameter = true; parameterLength = 1; // TODO break; case 'c': case 'e': case 'E': case 'g': case 'o': case 'u': case 'p': case 'n': handleNextParameter = true; parameterLength = 1; // TODO break; case 'd': i_d_x_f_found = true; parameterLength = 1; if (inputArgNr < parameters.size() && parameters[inputArgNr]->hasKnownIntValue()) parameterLength = std::to_string(parameters[inputArgNr]->getKnownIntValue()).length(); handleNextParameter = true; break; case 's': parameterLength = 0; if (inputArgNr < parameters.size() && parameters[inputArgNr]->tokType() == Token::eString) parameterLength = Token::getStrLength(parameters[inputArgNr]); handleNextParameter = true; break; } } if (formatString[i] == '%') percentCharFound = !percentCharFound; else if (percentCharFound) { digits_string.append(1, formatString[i]); } if (!percentCharFound) outputStringSize++; if (handleNextParameter) { // NOLINTNEXTLINE(cert-err34-c) - intentional use int tempDigits = std::abs(std::atoi(digits_string.c_str())); if (i_d_x_f_found) tempDigits = std::max(tempDigits, 1); if (digits_string.find('.') != std::string::npos) { const std::string endStr = digits_string.substr(digits_string.find('.') + 1); // NOLINTNEXTLINE(cert-err34-c) - intentional use const int maxLen = std::max(std::abs(std::atoi(endStr.c_str())), 1); if (formatString[i] == 's') { // For strings, the length after the dot "%.2s" will limit // the length of the string. if (parameterLength > maxLen) parameterLength = maxLen; } else { // For integers, the length after the dot "%.2d" can // increase required length if (tempDigits < maxLen) tempDigits = maxLen; } } if (tempDigits < parameterLength) outputStringSize += parameterLength; else outputStringSize += tempDigits; parameterLength = 0; digits_string.clear(); i_d_x_f_found = false; percentCharFound = false; handleNextParameter = false; ++inputArgNr; } } return outputStringSize; } //--------------------------------------------------------------------------- static bool getDimensionsEtc(const Token * const arrayToken, const Settings *settings, std::vector &dimensions, ErrorPath &errorPath, bool &mightBeLarger, MathLib::bigint &path) { const Token *array = arrayToken; while (Token::Match(array, ".|::")) array = array->astOperand2(); if (array->variable() && array->variable()->isArray() && !array->variable()->dimensions().empty()) { dimensions = array->variable()->dimensions(); if (dimensions[0].num <= 1 || !dimensions[0].tok) { visitAstNodes(arrayToken, [&](const Token *child) { if (child->originalName() == "->") { mightBeLarger = true; return ChildrenToVisit::none; } return ChildrenToVisit::op1_and_op2; }); } } else if (const Token *stringLiteral = array->getValueTokenMinStrSize(settings, &path)) { Dimension dim; dim.tok = nullptr; dim.num = Token::getStrArraySize(stringLiteral); dim.known = array->hasKnownValue(); dimensions.emplace_back(dim); } else if (array->valueType() && array->valueType()->pointer >= 1 && (array->valueType()->isIntegral() || array->valueType()->isFloat())) { const ValueFlow::Value *value = getBufferSizeValue(array); if (!value) return false; path = value->path; errorPath = value->errorPath; Dimension dim; dim.known = value->isKnown(); dim.tok = nullptr; const int typeSize = array->valueType()->typeSize(settings->platform, array->valueType()->pointer > 1); if (typeSize == 0) return false; dim.num = value->intvalue / typeSize; dimensions.emplace_back(dim); } return !dimensions.empty(); } static ValueFlow::Value makeSizeValue(MathLib::bigint size, MathLib::bigint path) { ValueFlow::Value v(size); v.path = path; return v; } static std::vector getOverrunIndexValues(const Token* tok, const Token* arrayToken, const std::vector& dimensions, const std::vector& indexTokens, MathLib::bigint path) { const Token *array = arrayToken; while (Token::Match(array, ".|::")) array = array->astOperand2(); bool isArrayIndex = tok->str() == "["; if (isArrayIndex) { const Token* parent = tok; while (Token::simpleMatch(parent, "[")) parent = parent->astParent(); if (!parent || parent->isUnaryOp("&")) isArrayIndex = false; } bool overflow = false; std::vector indexValues; for (int i = 0; i < dimensions.size() && i < indexTokens.size(); ++i) { MathLib::bigint size = dimensions[i].num; if (!isArrayIndex) size++; const bool zeroArray = array->variable() && array->variable()->isArray() && dimensions[i].num == 0; std::vector values = !zeroArray ? ValueFlow::isOutOfBounds(makeSizeValue(size, path), indexTokens[i]) : std::vector{}; if (values.empty()) { if (indexTokens[i]->hasKnownIntValue()) indexValues.push_back(indexTokens[i]->values().front()); else indexValues.push_back(ValueFlow::Value::unknown()); continue; } overflow = true; indexValues.push_back(values.front()); } if (overflow) return indexValues; return {}; } void CheckBufferOverrun::arrayIndex() { logChecker("CheckBufferOverrun::arrayIndex"); for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) { if (tok->str() != "[") continue; const Token *array = tok->astOperand1(); while (Token::Match(array, ".|::")) array = array->astOperand2(); if (!array || ((!array->variable() || array->variable()->nameToken() == array) && array->tokType() != Token::eString)) continue; if (!array->scope()->isExecutable()) { // LHS in non-executable scope => This is just a definition const Token *parent = tok; while (parent && !Token::simpleMatch(parent->astParent(), "=")) parent = parent->astParent(); if (!parent || parent == parent->astParent()->astOperand1()) continue; } if (astIsContainer(array)) continue; std::vector indexTokens; for (const Token *tok2 = tok; tok2 && tok2->str() == "["; tok2 = tok2->link()->next()) { if (!tok2->astOperand2()) { indexTokens.clear(); break; } indexTokens.emplace_back(tok2->astOperand2()); } if (indexTokens.empty()) continue; std::vector dimensions; ErrorPath errorPath; bool mightBeLarger = false; MathLib::bigint path = 0; if (!getDimensionsEtc(tok->astOperand1(), mSettings, dimensions, errorPath, mightBeLarger, path)) continue; const Variable* const var = array->variable(); if (var && var->isArgument() && var->scope()) { const Token* changeTok = var->scope()->bodyStart; bool isChanged = false; while ((changeTok = findVariableChanged(changeTok->next(), var->scope()->bodyEnd, /*indirect*/ 0, var->declarationId(), /*globalvar*/ false, mSettings, mTokenizer->isCPP()))) { if (!Token::simpleMatch(changeTok->astParent(), "[")) { isChanged = true; break; } } if (isChanged) continue; } // Positive index if (!mightBeLarger) { // TODO check arrays with dim 1 also const std::vector& indexValues = getOverrunIndexValues(tok, tok->astOperand1(), dimensions, indexTokens, path); if (!indexValues.empty()) arrayIndexError(tok, dimensions, indexValues); } // Negative index bool neg = false; std::vector negativeIndexes; for (const Token * indexToken : indexTokens) { const ValueFlow::Value *negativeValue = indexToken->getValueLE(-1, mSettings); if (negativeValue) { negativeIndexes.emplace_back(*negativeValue); neg = true; } else { negativeIndexes.emplace_back(ValueFlow::Value::unknown()); } } if (neg) { negativeIndexError(tok, dimensions, negativeIndexes); } } } static std::string stringifyIndexes(const std::string& array, const std::vector& indexValues) { if (indexValues.size() == 1) return std::to_string(indexValues[0].intvalue); std::ostringstream ret; ret << array; for (const ValueFlow::Value& index : indexValues) { ret << "["; if (index.isNonValue()) ret << "*"; else ret << index.intvalue; ret << "]"; } return ret.str(); } static std::string arrayIndexMessage(const Token* tok, const std::vector& dimensions, const std::vector& indexValues, const Token* condition) { auto add_dim = [](const std::string &s, const Dimension &dim) { return s + "[" + std::to_string(dim.num) + "]"; }; const std::string array = std::accumulate(dimensions.cbegin(), dimensions.cend(), tok->astOperand1()->expressionString(), add_dim); std::ostringstream errmsg; if (condition) errmsg << ValueFlow::eitherTheConditionIsRedundant(condition) << " or the array '" << array << "' is accessed at index " << stringifyIndexes(tok->astOperand1()->expressionString(), indexValues) << ", which is out of bounds."; else errmsg << "Array '" << array << "' accessed at index " << stringifyIndexes(tok->astOperand1()->expressionString(), indexValues) << ", which is out of bounds."; return errmsg.str(); } void CheckBufferOverrun::arrayIndexError(const Token* tok, const std::vector& dimensions, const std::vector& indexes) { if (!tok) { reportError(tok, Severity::error, "arrayIndexOutOfBounds", "Array 'arr[16]' accessed at index 16, which is out of bounds.", CWE_BUFFER_OVERRUN, Certainty::normal); reportError(tok, Severity::warning, "arrayIndexOutOfBoundsCond", "Array 'arr[16]' accessed at index 16, which is out of bounds.", CWE_BUFFER_OVERRUN, Certainty::normal); return; } const Token *condition = nullptr; const ValueFlow::Value *index = nullptr; for (const ValueFlow::Value& indexValue : indexes) { if (!indexValue.errorSeverity() && !mSettings->severity.isEnabled(Severity::warning)) return; if (indexValue.condition) condition = indexValue.condition; if (!index || !indexValue.errorPath.empty()) index = &indexValue; } reportError(getErrorPath(tok, index, "Array index out of bounds"), index->errorSeverity() ? Severity::error : Severity::warning, index->condition ? "arrayIndexOutOfBoundsCond" : "arrayIndexOutOfBounds", arrayIndexMessage(tok, dimensions, indexes, condition), CWE_BUFFER_OVERRUN, index->isInconclusive() ? Certainty::inconclusive : Certainty::normal); } void CheckBufferOverrun::negativeIndexError(const Token* tok, const std::vector& dimensions, const std::vector& indexes) { if (!tok) { reportError(tok, Severity::error, "negativeIndex", "Negative array index", CWE_BUFFER_UNDERRUN, Certainty::normal); return; } const Token *condition = nullptr; const ValueFlow::Value *negativeValue = nullptr; for (const ValueFlow::Value& indexValue : indexes) { if (!indexValue.errorSeverity() && !mSettings->severity.isEnabled(Severity::warning)) return; if (indexValue.condition) condition = indexValue.condition; if (!negativeValue || !indexValue.errorPath.empty()) negativeValue = &indexValue; } reportError(getErrorPath(tok, negativeValue, "Negative array index"), negativeValue->errorSeverity() ? Severity::error : Severity::warning, "negativeIndex", arrayIndexMessage(tok, dimensions, indexes, condition), CWE_BUFFER_UNDERRUN, negativeValue->isInconclusive() ? Certainty::inconclusive : Certainty::normal); } //--------------------------------------------------------------------------- void CheckBufferOverrun::pointerArithmetic() { if (!mSettings->severity.isEnabled(Severity::portability)) return; logChecker("CheckBufferOverrun::pointerArithmetic"); // portability for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) { if (!Token::Match(tok, "+|-")) continue; if (!tok->valueType() || tok->valueType()->pointer == 0) continue; if (!tok->isBinaryOp()) continue; if (!tok->astOperand1()->valueType() || !tok->astOperand2()->valueType()) continue; const Token *arrayToken, *indexToken; if (tok->astOperand1()->valueType()->pointer > 0) { arrayToken = tok->astOperand1(); indexToken = tok->astOperand2(); } else { arrayToken = tok->astOperand2(); indexToken = tok->astOperand1(); } if (!indexToken || !indexToken->valueType() || indexToken->valueType()->pointer > 0 || !indexToken->valueType()->isIntegral()) continue; std::vector dimensions; ErrorPath errorPath; bool mightBeLarger = false; MathLib::bigint path = 0; if (!getDimensionsEtc(arrayToken, mSettings, dimensions, errorPath, mightBeLarger, path)) continue; if (tok->str() == "+") { // Positive index if (!mightBeLarger) { // TODO check arrays with dim 1 also const std::vector indexTokens{indexToken}; const std::vector& indexValues = getOverrunIndexValues(tok, arrayToken, dimensions, indexTokens, path); if (!indexValues.empty()) pointerArithmeticError(tok, indexToken, &indexValues.front()); } if (const ValueFlow::Value *neg = indexToken->getValueLE(-1, mSettings)) pointerArithmeticError(tok, indexToken, neg); } else if (tok->str() == "-") { if (arrayToken->variable() && arrayToken->variable()->isArgument()) continue; const Token *array = arrayToken; while (Token::Match(array, ".|::")) array = array->astOperand2(); if (array->variable() && array->variable()->isArray()) { const ValueFlow::Value *v = indexToken->getValueGE(1, mSettings); if (v) pointerArithmeticError(tok, indexToken, v); } } } } void CheckBufferOverrun::pointerArithmeticError(const Token *tok, const Token *indexToken, const ValueFlow::Value *indexValue) { if (!tok) { reportError(tok, Severity::portability, "pointerOutOfBounds", "Pointer arithmetic overflow.", CWE_POINTER_ARITHMETIC_OVERFLOW, Certainty::normal); reportError(tok, Severity::portability, "pointerOutOfBoundsCond", "Pointer arithmetic overflow.", CWE_POINTER_ARITHMETIC_OVERFLOW, Certainty::normal); return; } std::string errmsg; if (indexValue->condition) errmsg = "Undefined behaviour, when '" + indexToken->expressionString() + "' is " + std::to_string(indexValue->intvalue) + " the pointer arithmetic '" + tok->expressionString() + "' is out of bounds."; else errmsg = "Undefined behaviour, pointer arithmetic '" + tok->expressionString() + "' is out of bounds."; reportError(getErrorPath(tok, indexValue, "Pointer arithmetic overflow"), Severity::portability, indexValue->condition ? "pointerOutOfBoundsCond" : "pointerOutOfBounds", errmsg, CWE_POINTER_ARITHMETIC_OVERFLOW, indexValue->isInconclusive() ? Certainty::inconclusive : Certainty::normal); } //--------------------------------------------------------------------------- ValueFlow::Value CheckBufferOverrun::getBufferSize(const Token *bufTok) const { if (!bufTok->valueType()) return ValueFlow::Value(-1); const Variable *var = bufTok->variable(); if (!var || var->dimensions().empty()) { const ValueFlow::Value *value = getBufferSizeValue(bufTok); if (value) return *value; } if (!var) return ValueFlow::Value(-1); const MathLib::bigint dim = std::accumulate(var->dimensions().cbegin(), var->dimensions().cend(), 1LL, [](MathLib::bigint i1, const Dimension &dim) { return i1 * dim.num; }); ValueFlow::Value v; v.setKnown(); v.valueType = ValueFlow::Value::ValueType::BUFFER_SIZE; if (var->isPointerArray()) v.intvalue = dim * mSettings->platform.sizeof_pointer; else if (var->isPointer()) return ValueFlow::Value(-1); else { const MathLib::bigint typeSize = bufTok->valueType()->typeSize(mSettings->platform); v.intvalue = dim * typeSize; } return v; } //--------------------------------------------------------------------------- static bool checkBufferSize(const Token *ftok, const Library::ArgumentChecks::MinSize &minsize, const std::vector &args, const MathLib::bigint bufferSize, const Settings *settings, const Tokenizer* tokenizer) { 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; 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; } break; case Library::ArgumentChecks::MinSize::Type::ARGVALUE: { if (arg && arg->hasKnownIntValue()) { MathLib::bigint myMinsize = arg->getKnownIntValue(); const unsigned int baseSize = tokenizer->sizeOfType(minsize.baseType); if (baseSize != 0) myMinsize *= baseSize; return myMinsize <= 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::VALUE: { MathLib::bigint myMinsize = minsize.value; const unsigned int baseSize = tokenizer->sizeOfType(minsize.baseType); if (baseSize != 0) myMinsize *= baseSize; return myMinsize <= bufferSize; } case Library::ArgumentChecks::MinSize::Type::NONE: break; } return true; } void CheckBufferOverrun::bufferOverflow() { logChecker("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, ") {")) continue; if (!mSettings->library.hasminsize(tok)) continue; const std::vector args = getArguments(tok); for (int argnr = 0; argnr < args.size(); ++argnr) { if (!args[argnr]->valueType() || args[argnr]->valueType()->pointer == 0) continue; const std::vector *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; if (argtok->valueType() && argtok->valueType()->pointer == 0) continue; // TODO: strcpy(buf+10, "hello"); const ValueFlow::Value bufferSize = getBufferSize(argtok); if (bufferSize.intvalue <= 0) continue; // buffer size == 1 => do not warn for dynamic memory if (bufferSize.intvalue == 1 && Token::simpleMatch(argtok->astParent(), ".")) { // TODO: check if parent was allocated dynamically const Token *tok2 = argtok; while (Token::simpleMatch(tok2->astParent(), ".")) tok2 = tok2->astParent(); while (Token::Match(tok2, "[|.")) tok2 = tok2->astOperand1(); const Variable *var = tok2 ? tok2->variable() : nullptr; if (var) { if (var->isPointer()) continue; if (var->isArgument() && var->isReference()) continue; } } const bool error = std::none_of(minsizes->begin(), minsizes->end(), [=](const Library::ArgumentChecks::MinSize &minsize) { return checkBufferSize(tok, minsize, args, bufferSize.intvalue, mSettings, mTokenizer); }); if (error) bufferOverflowError(args[argnr], &bufferSize, Certainty::normal); } } } } void CheckBufferOverrun::bufferOverflowError(const Token *tok, const ValueFlow::Value *value, Certainty certainty) { reportError(getErrorPath(tok, value, "Buffer overrun"), Severity::error, "bufferAccessOutOfBounds", "Buffer is accessed out of bounds: " + (tok ? tok->expressionString() : "buf"), CWE_BUFFER_OVERRUN, certainty); } //--------------------------------------------------------------------------- void CheckBufferOverrun::arrayIndexThenCheck() { if (!mSettings->severity.isEnabled(Severity::portability)) return; logChecker("CheckBufferOverrun::arrayIndexThenCheck"); 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 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 && tok2->str() != "?") { 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.", CWE_ARRAY_INDEX_THEN_CHECK, Certainty::normal); } //--------------------------------------------------------------------------- void CheckBufferOverrun::stringNotZeroTerminated() { // this is currently 'inconclusive'. See TestBufferOverrun::terminateStrncpy3 if (!mSettings->severity.isEnabled(Severity::warning) || !mSettings->certainty.isEnabled(Certainty::inconclusive)) return; logChecker("CheckBufferOverrun::stringNotZeroTerminated"); // warning,inconclusive 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, "strncpy (")) continue; const std::vector args = getArguments(tok); if (args.size() != 3) continue; const Token *sizeToken = args[2]; if (!sizeToken->hasKnownIntValue()) continue; const ValueFlow::Value &bufferSize = getBufferSize(args[0]); if (bufferSize.intvalue < 0 || sizeToken->getKnownIntValue() < bufferSize.intvalue) continue; if (Token::simpleMatch(args[1], "(") && Token::simpleMatch(args[1]->astOperand1(), ". c_str") && args[1]->astOperand1()->astOperand1()) { const std::list& contValues = args[1]->astOperand1()->astOperand1()->values(); auto it = std::find_if(contValues.cbegin(), contValues.cend(), [](const ValueFlow::Value& value) { return value.isContainerSizeValue() && !value.isImpossible(); }); if (it != contValues.end() && it->intvalue < sizeToken->getKnownIntValue()) continue; } else { 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::simpleMatch(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.. terminateStrncpyError(tok, args[0]->expressionString()); } } } 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, Certainty::inconclusive); } //--------------------------------------------------------------------------- void CheckBufferOverrun::argumentSize() { // Check '%type% x[10]' arguments if (!mSettings->severity.isEnabled(Severity::warning)) return; logChecker("CheckBufferOverrun::argumentSize"); // warning const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); for (const Scope * const scope : symbolDatabase->functionScopes) { for (const Token *tok = scope->bodyStart; tok != scope->bodyEnd; tok = tok->next()) { if (!tok->function() || !Token::Match(tok, "%name% (")) continue; // If argument is '%type% a[num]' then check bounds against num const Function *callfunc = tok->function(); const std::vector callargs = getArguments(tok); for (nonneg int paramIndex = 0; paramIndex < callargs.size() && paramIndex < callfunc->argCount(); ++paramIndex) { const Variable* const argument = callfunc->getArgumentVar(paramIndex); if (!argument || !argument->nameToken() || !argument->isArray()) continue; if (!argument->valueType() || !callargs[paramIndex]->valueType()) continue; if (argument->valueType()->type != callargs[paramIndex]->valueType()->type) continue; const Token * calldata = callargs[paramIndex]; while (Token::Match(calldata, "::|.")) calldata = calldata->astOperand2(); if (!calldata->variable() || !calldata->variable()->isArray()) continue; if (calldata->variable()->dimensions().size() != argument->dimensions().size()) continue; bool err = false; for (int d = 0; d < argument->dimensions().size(); ++d) { const auto& dim1 = calldata->variable()->dimensions()[d]; const auto& dim2 = argument->dimensions()[d]; if (!dim1.known || !dim2.known) break; if (dim1.num < dim2.num) err = true; } if (err) argumentSizeError(tok, tok->str(), paramIndex, callargs[paramIndex]->expressionString(), calldata->variable(), argument); } } } } void CheckBufferOverrun::argumentSizeError(const Token *tok, const std::string &functionName, nonneg int paramIndex, const std::string ¶mExpression, const Variable *paramVar, const Variable *functionArg) { const std::string strParamNum = std::to_string(paramIndex + 1) + getOrdinalText(paramIndex + 1); ErrorPath errorPath; errorPath.emplace_back(tok, "Function '" + functionName + "' is called"); if (functionArg) errorPath.emplace_back(functionArg->nameToken(), "Declaration of " + strParamNum + " function argument."); if (paramVar) errorPath.emplace_back(paramVar->nameToken(), "Passing buffer '" + paramVar->name() + "' to function that is declared here"); errorPath.emplace_back(tok, ""); reportError(errorPath, Severity::warning, "argumentSize", "$symbol:" + functionName + '\n' + "Buffer '" + paramExpression + "' is too small, the function '" + functionName + "' expects a bigger buffer in " + strParamNum + " argument", CWE_ARGUMENT_SIZE, Certainty::normal); } //--------------------------------------------------------------------------- // CTU.. //--------------------------------------------------------------------------- // a Clang-built executable will crash when using the anonymous MyFileInfo later on - so put it in a unique namespace for now // see https://trac.cppcheck.net/ticket/12108 for more details #ifdef __clang__ inline namespace CheckBufferOverrun_internal #else namespace #endif { /** data for multifile checking */ class MyFileInfo : public Check::FileInfo { public: /** unsafe array index usage */ std::list unsafeArrayIndex; /** unsafe pointer arithmetics */ std::list unsafePointerArith; /** Convert data into xml string */ std::string toString() const override { std::string xml; if (!unsafeArrayIndex.empty()) xml = " \n" + CTU::toString(unsafeArrayIndex) + " \n"; if (!unsafePointerArith.empty()) xml += " \n" + CTU::toString(unsafePointerArith) + " \n"; return xml; } }; } bool CheckBufferOverrun::isCtuUnsafeBufferUsage(const Settings *settings, const Token *argtok, MathLib::bigint *offset, int type) { if (!offset) return false; if (!argtok->valueType() || argtok->valueType()->typeSize(settings->platform) == 0) return false; const Token *indexTok = nullptr; if (type == 1 && Token::Match(argtok, "%name% [") && argtok->astParent() == argtok->next() && !Token::simpleMatch(argtok->linkAt(1), "] [")) indexTok = argtok->next()->astOperand2(); else if (type == 2 && Token::simpleMatch(argtok->astParent(), "+")) indexTok = (argtok == argtok->astParent()->astOperand1()) ? argtok->astParent()->astOperand2() : argtok->astParent()->astOperand1(); if (!indexTok) return false; if (!indexTok->hasKnownIntValue()) return false; *offset = indexTok->getKnownIntValue() * argtok->valueType()->typeSize(settings->platform); return true; } bool CheckBufferOverrun::isCtuUnsafeArrayIndex(const Settings *settings, const Token *argtok, MathLib::bigint *offset) { return isCtuUnsafeBufferUsage(settings, argtok, offset, 1); } bool CheckBufferOverrun::isCtuUnsafePointerArith(const Settings *settings, const Token *argtok, MathLib::bigint *offset) { return isCtuUnsafeBufferUsage(settings, argtok, offset, 2); } /** @brief Parse current TU and extract file info */ Check::FileInfo *CheckBufferOverrun::getFileInfo(const Tokenizer *tokenizer, const Settings *settings) const { auto *fileInfo = new MyFileInfo; fileInfo->unsafeArrayIndex = CTU::getUnsafeUsage(tokenizer, settings, isCtuUnsafeArrayIndex); fileInfo->unsafePointerArith = CTU::getUnsafeUsage(tokenizer, settings, isCtuUnsafePointerArith); if (fileInfo->unsafeArrayIndex.empty() && fileInfo->unsafePointerArith.empty()) { delete fileInfo; return nullptr; } return fileInfo; } Check::FileInfo * CheckBufferOverrun::loadFileInfoFromXml(const tinyxml2::XMLElement *xmlElement) const { // cppcheck-suppress shadowFunction - TODO: fix this const std::string arrayIndex("array-index"); const std::string pointerArith("pointer-arith"); auto *fileInfo = new MyFileInfo; for (const tinyxml2::XMLElement *e = xmlElement->FirstChildElement(); e; e = e->NextSiblingElement()) { if (e->Name() == arrayIndex) fileInfo->unsafeArrayIndex = CTU::loadUnsafeUsageListFromXml(e); else if (e->Name() == pointerArith) fileInfo->unsafePointerArith = CTU::loadUnsafeUsageListFromXml(e); } if (fileInfo->unsafeArrayIndex.empty() && fileInfo->unsafePointerArith.empty()) { delete fileInfo; return nullptr; } return fileInfo; } /** @brief Analyse all file infos for all TU */ bool CheckBufferOverrun::analyseWholeProgram(const CTU::FileInfo *ctu, const std::list &fileInfo, const Settings& settings, ErrorLogger &errorLogger) { if (!ctu) return false; bool foundErrors = false; (void)settings; // This argument is unused CheckBufferOverrun dummy(nullptr, &settings, &errorLogger); dummy. logChecker("CheckBufferOverrun::analyseWholeProgram"); const std::map> callsMap = ctu->getCallsMap(); for (const Check::FileInfo* fi1 : fileInfo) { const MyFileInfo *fi = dynamic_cast(fi1); if (!fi) continue; for (const CTU::FileInfo::UnsafeUsage &unsafeUsage : fi->unsafeArrayIndex) foundErrors |= analyseWholeProgram1(callsMap, unsafeUsage, 1, errorLogger); for (const CTU::FileInfo::UnsafeUsage &unsafeUsage : fi->unsafePointerArith) foundErrors |= analyseWholeProgram1(callsMap, unsafeUsage, 2, errorLogger); } return foundErrors; } bool CheckBufferOverrun::analyseWholeProgram1(const std::map> &callsMap, const CTU::FileInfo::UnsafeUsage &unsafeUsage, int type, ErrorLogger &errorLogger) { const CTU::FileInfo::FunctionCall *functionCall = nullptr; const std::list &locationList = CTU::FileInfo::getErrorPath(CTU::FileInfo::InvalidValueType::bufferOverflow, unsafeUsage, callsMap, "Using argument ARG", &functionCall, false); if (locationList.empty()) return false; const char *errorId = nullptr; std::string errmsg; CWE cwe(0); if (type == 1) { errorId = "ctuArrayIndex"; if (unsafeUsage.value > 0) errmsg = "Array index out of bounds; '" + unsafeUsage.myArgumentName + "' buffer size is " + std::to_string(functionCall->callArgValue) + " and it is accessed at offset " + std::to_string(unsafeUsage.value) + "."; else errmsg = "Array index out of bounds; buffer '" + unsafeUsage.myArgumentName + "' is accessed at offset " + std::to_string(unsafeUsage.value) + "."; cwe = (unsafeUsage.value > 0) ? CWE_BUFFER_OVERRUN : CWE_BUFFER_UNDERRUN; } else { errorId = "ctuPointerArith"; errmsg = "Pointer arithmetic overflow; '" + unsafeUsage.myArgumentName + "' buffer size is " + std::to_string(functionCall->callArgValue); cwe = CWE_POINTER_ARITHMETIC_OVERFLOW; } const ErrorMessage errorMessage(locationList, emptyString, Severity::error, errmsg, errorId, cwe, Certainty::normal); errorLogger.reportErr(errorMessage); return true; } void CheckBufferOverrun::objectIndex() { logChecker("CheckBufferOverrun::objectIndex"); const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); for (const Scope *functionScope : symbolDatabase->functionScopes) { for (const Token *tok = functionScope->bodyStart; tok != functionScope->bodyEnd; tok = tok->next()) { if (!Token::simpleMatch(tok, "[")) continue; const Token *obj = tok->astOperand1(); const Token *idx = tok->astOperand2(); if (!idx || !obj) continue; if (idx->hasKnownIntValue()) { if (idx->getKnownIntValue() == 0) continue; } if (idx->hasKnownIntValue() && idx->getKnownIntValue() == 0) continue; std::vector values = ValueFlow::getLifetimeObjValues(obj, false, -1); for (const ValueFlow::Value& v:values) { if (v.lifetimeKind != ValueFlow::Value::LifetimeKind::Address) continue; const Variable *var = v.tokvalue->variable(); if (!var) continue; if (var->isReference()) continue; if (var->isRValueReference()) continue; if (var->isArray()) continue; if (var->isPointer()) { if (!var->valueType()) continue; if (!obj->valueType()) continue; if (var->valueType()->pointer > obj->valueType()->pointer) continue; } if (obj->valueType() && var->valueType() && (obj->isCast() || (mTokenizer->isCPP() && isCPPCast(obj)) || obj->valueType()->pointer)) { // allow cast to a different type const auto varSize = var->valueType()->typeSize(mSettings->platform); if (varSize == 0) continue; if (obj->valueType()->type != var->valueType()->type) { if (ValueFlow::isOutOfBounds(makeSizeValue(varSize, v.path), idx).empty()) continue; } } if (v.path != 0) { std::vector idxValues; std::copy_if(idx->values().cbegin(), idx->values().cend(), std::back_inserter(idxValues), [&](const ValueFlow::Value& vidx) { if (!vidx.isIntValue()) return false; return vidx.path == v.path || vidx.path == 0; }); if (std::any_of(idxValues.cbegin(), idxValues.cend(), [&](const ValueFlow::Value& vidx) { if (vidx.isImpossible()) return (vidx.intvalue == 0); return (vidx.intvalue != 0); })) { objectIndexError(tok, &v, idx->hasKnownIntValue()); } } else { objectIndexError(tok, &v, idx->hasKnownIntValue()); } } } } } void CheckBufferOverrun::objectIndexError(const Token *tok, const ValueFlow::Value *v, bool known) { ErrorPath errorPath; std::string name; if (v) { name = v->tokvalue->variable()->name(); errorPath = v->errorPath; } errorPath.emplace_back(tok, ""); std::string verb = known ? "is" : "might be"; reportError(errorPath, known ? Severity::error : Severity::warning, "objectIndex", "The address of local variable '" + name + "' " + verb + " accessed at non-zero index.", CWE758, Certainty::normal); } static bool isVLAIndex(const Token* tok) { if (!tok) return false; if (tok->varId() != 0U) return true; if (tok->str() == "?") { // this is a VLA index if both expressions around the ":" is VLA index return tok->astOperand2() && tok->astOperand2()->str() == ":" && isVLAIndex(tok->astOperand2()->astOperand1()) && isVLAIndex(tok->astOperand2()->astOperand2()); } return isVLAIndex(tok->astOperand1()) || isVLAIndex(tok->astOperand2()); } void CheckBufferOverrun::negativeArraySize() { logChecker("CheckBufferOverrun::negativeArraySize"); const SymbolDatabase* symbolDatabase = mTokenizer->getSymbolDatabase(); for (const Variable* var : symbolDatabase->variableList()) { if (!var || !var->isArray()) continue; const Token* const nameToken = var->nameToken(); if (!Token::Match(nameToken, "%var% [") || !nameToken->next()->astOperand2()) continue; const ValueFlow::Value* sz = nameToken->next()->astOperand2()->getValueLE(-1, mSettings); // don't warn about constant negative index because that is a compiler error if (sz && isVLAIndex(nameToken->next()->astOperand2())) negativeArraySizeError(nameToken); } for (const Scope* functionScope : symbolDatabase->functionScopes) { for (const Token* tok = functionScope->bodyStart; tok != functionScope->bodyEnd; tok = tok->next()) { if (!tok->isKeyword() || tok->str() != "new" || !tok->astOperand1() || tok->astOperand1()->str() != "[") continue; const Token* valOperand = tok->astOperand1()->astOperand2(); if (!valOperand) continue; const ValueFlow::Value* sz = valOperand->getValueLE(-1, mSettings); if (sz) negativeMemoryAllocationSizeError(tok, sz); } } } void CheckBufferOverrun::negativeArraySizeError(const Token* tok) { const std::string arrayName = tok ? tok->expressionString() : std::string(); const std::string line1 = arrayName.empty() ? std::string() : ("$symbol:" + arrayName + '\n'); reportError(tok, Severity::error, "negativeArraySize", line1 + "Declaration of array '" + arrayName + "' with negative size is undefined behaviour", CWE758, Certainty::normal); } void CheckBufferOverrun::negativeMemoryAllocationSizeError(const Token* tok, const ValueFlow::Value* value) { const std::string msg = "Memory allocation size is negative."; const ErrorPath errorPath = getErrorPath(tok, value, msg); const bool inconclusive = value != nullptr && !value->isKnown(); reportError(errorPath, inconclusive ? Severity::warning : Severity::error, "negativeMemoryAllocationSize", msg, CWE131, inconclusive ? Certainty::inconclusive : Certainty::normal); }