Replaced check for pipe() buffer size by ordinary CheckBufferOverrun, provide required Library configuration option (#4183)

Merged from LCppC.
This commit is contained in:
PKEuS 2022-06-19 12:01:55 +02:00 committed by GitHub
parent 6873f5237b
commit 9eb16e1002
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 91 additions and 169 deletions

View File

@ -306,6 +306,9 @@
<attribute name="value">
<ref name="MINSIZE-VALUE"/>
</attribute>
<optional>
<attribute name="baseType"><text/></attribute>
</optional>
</element>
</choice>
</zeroOrMore>

View File

@ -414,7 +414,7 @@
<arg nr="1" direction="out">
<not-null/>
<not-bool/>
<minsize type="value" value="2"/>
<minsize type="value" value="2" baseType="int"/>
</arg>
<arg nr="2" direction="in">
<not-uninit/>

View File

@ -1742,7 +1742,7 @@ The function 'mktemp' is considered to be dangerous due to race conditions and s
<arg nr="2" direction="in">
<not-uninit/>
<not-bool/>
<minsize type="value" value="2"/>
<minsize type="value" value="2" baseType="timespec"/>
</arg>
</function>
<!-- int utimensat(int dirfd, const char *pathname, const struct timespec times[2], int flags);-->
@ -1762,7 +1762,7 @@ The function 'mktemp' is considered to be dangerous due to race conditions and s
<arg nr="3" direction="in">
<not-uninit/>
<not-bool/>
<minsize type="value" value="2"/>
<minsize type="value" value="2" baseType="timespec"/>
</arg>
<arg nr="4" direction="in">
<not-uninit/>
@ -1782,7 +1782,7 @@ The function 'mktemp' is considered to be dangerous due to race conditions and s
<arg nr="2" direction="in">
<not-uninit/>
<not-bool/>
<minsize type="value" value="2"/>
<minsize type="value" value="2" baseType="timeval"/>
</arg>
<warn severity="style" reason="Obsolescent" alternatives="utimensat"/>
</function>
@ -2749,7 +2749,7 @@ The function 'mktemp' is considered to be dangerous due to race conditions and s
<arg nr="4" direction="out">
<not-null/>
<not-bool/>
<minsize type="value" value="2"/>
<minsize type="value" value="2" baseType="int"/>
</arg>
</function>
<!-- http://man7.org/linux/man-pages/man2/socketpair.2.html -->
@ -2761,7 +2761,7 @@ The function 'mktemp' is considered to be dangerous due to race conditions and s
<arg nr="1" direction="out">
<not-null/>
<not-bool/>
<minsize type="value" value="2"/>
<minsize type="value" value="2" baseType="int"/>
</arg>
</function>
<!-- int pselect(int nfds, fd_set *restrict readfds,

View File

@ -563,7 +563,7 @@ ValueFlow::Value CheckBufferOverrun::getBufferSize(const Token *bufTok) const
}
//---------------------------------------------------------------------------
static bool checkBufferSize(const Token *ftok, const Library::ArgumentChecks::MinSize &minsize, const std::vector<const Token *> &args, const MathLib::bigint bufferSize, const Settings *settings)
static bool checkBufferSize(const Token *ftok, const Library::ArgumentChecks::MinSize &minsize, const std::vector<const Token *> &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;
@ -589,8 +589,13 @@ static bool checkBufferSize(const Token *ftok, const Library::ArgumentChecks::Mi
if (arg && arg2 && arg->hasKnownIntValue() && arg2->hasKnownIntValue())
return (arg->getKnownIntValue() * arg2->getKnownIntValue()) <= bufferSize;
break;
case Library::ArgumentChecks::MinSize::Type::VALUE:
return minsize.value <= bufferSize;
case Library::ArgumentChecks::MinSize::Type::VALUE: {
MathLib::bigint myMinsize = minsize.value;
unsigned int baseSize = tokenizer->sizeOfType(minsize.baseType);
if (baseSize != 0)
myMinsize *= baseSize;
return myMinsize <= bufferSize;
}
case Library::ArgumentChecks::MinSize::Type::NONE:
break;
}
@ -644,7 +649,7 @@ void CheckBufferOverrun::bufferOverflow()
}
}
const bool error = std::none_of(minsizes->begin(), minsizes->end(), [=](const Library::ArgumentChecks::MinSize &minsize) {
return checkBufferSize(tok, minsize, args, bufferSize.intvalue, mSettings);
return checkBufferSize(tok, minsize, args, bufferSize.intvalue, mSettings, mTokenizer);
});
if (error)
bufferOverflowError(args[argnr], &bufferSize, (bufferSize.intvalue == 1) ? Certainty::inconclusive : Certainty::normal);

View File

@ -384,47 +384,6 @@ void CheckOther::invalidPointerCastError(const Token* tok, const std::string& fr
reportError(tok, Severity::portability, "invalidPointerCast", "Casting between " + from + " and " + to + " which have an incompatible binary data representation.", CWE704, Certainty::normal);
}
//---------------------------------------------------------------------------
// This check detects errors on POSIX systems, when a pipe command called
// with a wrong dimensioned file descriptor array. The pipe command requires
// exactly an integer array of dimension two as parameter.
//
// References:
// - http://linux.die.net/man/2/pipe
// - ticket #3521
//---------------------------------------------------------------------------
void CheckOther::checkPipeParameterSize()
{
if (!mSettings->posix())
return;
const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();
for (const Scope * scope : symbolDatabase->functionScopes) {
for (const Token* tok = scope->bodyStart->next(); tok != scope->bodyEnd; tok = tok->next()) {
if (Token::Match(tok, "pipe ( %var% )") ||
Token::Match(tok, "pipe2 ( %var% ,")) {
const Token * const varTok = tok->tokAt(2);
const Variable *var = varTok->variable();
MathLib::bigint dim;
if (var && var->isArray() && !var->isArgument() && ((dim=var->dimension(0U)) < 2)) {
const std::string strDim = MathLib::toString(dim);
checkPipeParameterSizeError(varTok,varTok->str(), strDim);
}
}
}
}
}
void CheckOther::checkPipeParameterSizeError(const Token *tok, const std::string &strVarName, const std::string &strDim)
{
reportError(tok, Severity::error,
"wrongPipeParameterSize",
"$symbol:" + strVarName + "\n"
"Buffer '$symbol' must have size of 2 integers if used as parameter of pipe().\n"
"The pipe()/pipe2() system command takes an argument, which is an array of exactly two integers.\n"
"The variable '$symbol' is an array of size " + strDim + ", which does not match.", CWE686, Certainty::safe);
}
//---------------------------------------------------------------------------
// Detect redundant assignments: x = 0; x = 4;

View File

@ -87,7 +87,6 @@ public:
checkOther.checkKnownArgument();
checkOther.checkComparePointers();
checkOther.checkIncompleteStatement();
checkOther.checkPipeParameterSize();
checkOther.checkRedundantCopy();
checkOther.clarifyCalculation();
checkOther.checkPassByReference();
@ -190,9 +189,6 @@ public:
/** @brief %Check that variadic function calls don't use NULL. If NULL is \#defined as 0 and the function expects a pointer, the behaviour is undefined. */
void checkVarFuncNullUB();
/** @brief %Check that calling the POSIX pipe() system call is called with an integer array of size two. */
void checkPipeParameterSize();
/** @brief %Check to avoid casting a return value to unsigned char and then back to integer type. */
void checkCastIntToCharAndBack();
@ -234,7 +230,6 @@ private:
// Error messages..
void checkComparisonFunctionIsAlwaysTrueOrFalseError(const Token* tok, const std::string &functionName, const std::string &varName, const bool result);
void checkCastIntToCharAndBackError(const Token *tok, const std::string &strFunctionName);
void checkPipeParameterSizeError(const Token *tok, const std::string &strVarName, const std::string &strDim);
void clarifyCalculationError(const Token *tok, const std::string &op);
void clarifyStatementError(const Token* tok);
void cstyleCastError(const Token *tok);
@ -299,7 +294,6 @@ private:
c.invalidPointerCastError(nullptr, "float *", "double *", false, false);
c.negativeBitwiseShiftError(nullptr, 1);
c.negativeBitwiseShiftError(nullptr, 2);
c.checkPipeParameterSizeError(nullptr, "varname", "dimension");
c.raceAfterInterlockedDecrementError(nullptr);
c.invalidFreeError(nullptr, "malloc", false);
c.overlappingWriteUnion(nullptr);
@ -378,7 +372,6 @@ private:
"- assignment in an assert statement\n"
"- free() or delete of an invalid memory location\n"
"- bitwise operation with negative right operand\n"
"- provide wrong dimensioned array to pipe() system command (--std=posix)\n"
"- cast the return values of getc(),fgetc() and getchar() to character and compare it to EOF\n"
"- race condition with non-interlocked access after InterlockedDecrement() call\n"
"- expression 'x = x++;' depends on order of evaluation of side effects\n"

View File

@ -782,6 +782,9 @@ Library::Error Library::loadFunction(const tinyxml2::XMLElement * const node, co
return Error(ErrorCode::BAD_ATTRIBUTE_VALUE, valueattr);
ac.minsizes.emplace_back(type, 0);
ac.minsizes.back().value = minsizevalue;
const char* baseTypeAttr = argnode->Attribute("baseType");
if (baseTypeAttr)
ac.minsizes.back().baseType = baseTypeAttr;
} else {
const char *argattr = argnode->Attribute("arg");
if (!argattr)

View File

@ -327,6 +327,7 @@ public:
int arg;
int arg2;
long long value;
std::string baseType;
};
std::vector<MinSize> minsizes;

View File

@ -203,6 +203,19 @@ Tokenizer::~Tokenizer()
// SizeOfType - gives the size of a type
//---------------------------------------------------------------------------
nonneg int Tokenizer::sizeOfType(const std::string& type) const
{
const std::map<std::string, int>::const_iterator it = mTypeSize.find(type);
if (it == mTypeSize.end()) {
const Library::PodType* podtype = mSettings->library.podtype(type);
if (!podtype)
return 0;
return podtype->size;
}
return it->second;
}
nonneg int Tokenizer::sizeOfType(const Token *type) const
{
if (!type || type->str().empty())

View File

@ -182,6 +182,7 @@ public:
* @return sizeof for given type, or 0 if it can't be calculated.
*/
nonneg int sizeOfType(const Token* type) const;
nonneg int sizeOfType(const std::string& type) const;
void simplifyDebug();
/**

View File

@ -322,6 +322,8 @@ private:
TEST_CASE(ctu_arithmetic);
TEST_CASE(objectIndex);
TEST_CASE(checkPipeParameterSize); // ticket #3521
}
@ -5227,6 +5229,44 @@ private:
"}\n");
ASSERT_EQUALS("", errout.str());
}
void checkPipeParameterSize() { // #3521
Settings settings;
LOAD_LIB_2(settings.library, "posix.cfg");
check("void f(){\n"
"int pipefd[1];\n" // <-- array of two integers is needed
"if (pipe(pipefd) == -1) {\n"
" return;\n"
" }\n"
"}", settings);
ASSERT_EQUALS("[test.cpp:3]: (error) Buffer is accessed out of bounds: pipefd\n", errout.str());
check("void f(){\n"
"int pipefd[2];\n"
"if (pipe(pipefd) == -1) {\n"
" return;\n"
" }\n"
"}", settings);
ASSERT_EQUALS("", errout.str());
check("void f(){\n"
"char pipefd[2];\n"
"if (pipe((int*)pipefd) == -1) {\n"
" return;\n"
" }\n"
"}", settings);
ASSERT_EQUALS("[test.cpp:3]: (error) Buffer is accessed out of bounds: (int*)pipefd\n", errout.str());
check("void f(){\n"
"char pipefd[20];\n" // Strange, but large enough
"if (pipe((int*)pipefd) == -1) {\n"
" return;\n"
" }\n"
"}", settings);
ASSERT_EQUALS("", errout.str());
}
};
REGISTER_TEST(TestBufferOverrun)

View File

@ -481,6 +481,7 @@ private:
" <arg nr=\"2\"><minsize type=\"argvalue\" arg=\"3\"/></arg>\n"
" <arg nr=\"3\"/>\n"
" <arg nr=\"4\"><minsize type=\"value\" value=\"500\"/></arg>\n"
" <arg nr=\"5\"><minsize type=\"value\" value=\"4\" baseType=\"int\"/></arg>\n"
" </function>\n"
"</def>";
@ -488,7 +489,7 @@ private:
ASSERT_EQUALS(true, Library::ErrorCode::OK == (readLibrary(library, xmldata)).errorcode);
TokenList tokenList(nullptr);
std::istringstream istr("foo(a,b,c,d);");
std::istringstream istr("foo(a,b,c,d,e);");
tokenList.createTokens(istr);
tokenList.front()->next()->astOperand1(tokenList.front());
@ -520,6 +521,18 @@ private:
const Library::ArgumentChecks::MinSize &m = minsizes->front();
ASSERT(Library::ArgumentChecks::MinSize::Type::VALUE == m.type);
ASSERT_EQUALS(500, m.value);
ASSERT_EQUALS(emptyString, m.baseType);
}
// arg5: type=value
minsizes = library.argminsizes(tokenList.front(), 5);
ASSERT_EQUALS(true, minsizes != nullptr);
ASSERT_EQUALS(1U, minsizes ? minsizes->size() : 1U);
if (minsizes && minsizes->size() == 1U) {
const Library::ArgumentChecks::MinSize& m = minsizes->front();
ASSERT(Library::ArgumentChecks::MinSize::Type::VALUE == m.type);
ASSERT_EQUALS(4, m.value);
ASSERT_EQUALS("int", m.baseType);
}
}

View File

@ -208,8 +208,6 @@ private:
TEST_CASE(varFuncNullUB);
TEST_CASE(checkPipeParameterSize); // ticket #3521
TEST_CASE(checkCastIntToCharAndBack); // ticket #160
TEST_CASE(checkCommaSeparatedReturn);
@ -350,20 +348,6 @@ private:
checkOther.runChecks(&tokenizer, settings, this);
}
void checkposix(const char code[]) {
static Settings settings;
settings.severity.enable(Severity::warning);
settings.libraries.emplace_back("posix");
check(code,
nullptr, // filename
false, // experimental
false, // inconclusive
true, // runSimpleChecks
false, // verbose
&settings);
}
void checkInterlockedDecrement(const char code[]) {
static Settings settings;
settings.platformType = Settings::Win32A;
@ -8627,99 +8611,6 @@ private:
ASSERT_EQUALS("", errout.str());
}
void checkPipeParameterSize() { // #3521
checkposix("void f(){\n"
"int pipefd[1];\n" // <-- array of two integers is needed
"if (pipe(pipefd) == -1) {\n"
" return;\n"
" }\n"
"}");
ASSERT_EQUALS("[test.cpp:3]: (error) Buffer 'pipefd' must have size of 2 integers if used as parameter of pipe().\n", errout.str());
checkposix("void f(){\n"
"int pipefd[2];\n"
"if (pipe(pipefd) == -1) {\n"
" return;\n"
" }\n"
"}");
ASSERT_EQUALS("", errout.str());
checkposix("void f(){\n"
"int pipefd[20];\n"
"if (pipe(pipefd) == -1) {\n"
" return;\n"
" }\n"
"}");
ASSERT_EQUALS("", errout.str());
checkposix("void f(){\n"
"int pipefd[1];\n" // <-- array of two integers is needed
"if (pipe2(pipefd,0) == -1) {\n"
" return;\n"
" }\n"
"}");
ASSERT_EQUALS("[test.cpp:3]: (error) Buffer 'pipefd' must have size of 2 integers if used as parameter of pipe().\n", errout.str());
checkposix("void f(){\n"
"int pipefd[2];\n"
"if (pipe2(pipefd,0) == -1) {\n"
" return;\n"
" }\n"
"}");
ASSERT_EQUALS("", errout.str());
checkposix("void f(){\n"
"int pipefd[20];\n"
"if (pipe2(pipefd,0) == -1) {\n"
" return;\n"
" }\n"
"}");
ASSERT_EQUALS("", errout.str());
// avoid crash with pointer variable
check("void foo (int* arrayPtr)\n"
"{\n"
" if (pipe (arrayPtr) < 0)\n"
" {}\n"
"}");
ASSERT_EQUALS("", errout.str());
// avoid crash with pointer variable - for local variable on stack as well - see #4801
check("void foo() {\n"
" int *cp;\n"
" if ( pipe (cp) == -1 ) {\n"
" return;\n"
" }\n"
"}");
ASSERT_EQUALS("", errout.str());
// test with unknown variable
check("void foo() {\n"
" if ( pipe (cp) == -1 ) {\n"
" return;\n"
" }\n"
"}");
ASSERT_EQUALS("", errout.str());
// avoid crash with pointer variable - for local variable on stack as well - see #4801
check("void foo() {\n"
" int *cp;\n"
" if ( pipe (cp) == -1 ) {\n"
" return;\n"
" }\n"
"}");
ASSERT_EQUALS("", errout.str());
// test with unknown variable
check("void foo() {\n"
" if ( pipe (cp) == -1 ) {\n"
" return;\n"
" }\n"
"}");
ASSERT_EQUALS("", errout.str());
}
void checkCastIntToCharAndBack() { // #160
// check getchar