diff --git a/clang/docs/analyzer/checkers.rst b/clang/docs/analyzer/checkers.rst --- a/clang/docs/analyzer/checkers.rst +++ b/clang/docs/analyzer/checkers.rst @@ -2053,6 +2053,7 @@ SEI CERT checkers which tries to find errors based on their `C coding rules `_. + .. _alpha-security-cert-pos-checkers: alpha.security.cert.pos @@ -2074,10 +2075,38 @@ if (retval < 0 || (size_t)retval >= sizeof(env)) { /* Handle error */ } - + return putenv(env); // putenv function should not be called with auto variables } - + +alpha.security.cert.env +^^^^^^^^^^^^^^^^^^^^^^^ + +SEI CERT checkers of `POSIX C coding rules `_. + +.. _alpha-security-cert-env-31c: + +alpha.security.cert.env.31c +""""""""""""""""""""""""""" +Finds usage of possibly invalidated environment pointer. + +.. code-block:: c + + int main(int argc, const char *argv[], const char *envp[]) { + if (setenv("MY_NEW_VAR", "new_value", 1) != 0) { + // setenv call may invalidate 'envp' + /* Handle error */ + } + if (envp != NULL) { + for (size_t i = 0; envp[i] != NULL; ++i) { + puts(envp[i]); + // envp may no longer point to the current environment + // this program has unanticipated behavior. + } + } + return 0; + } + .. _alpha-security-ArrayBound: alpha.security.ArrayBound (C) diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -73,6 +73,7 @@ def CERT : Package<"cert">, ParentPackage; def POS : Package<"pos">, ParentPackage; +def ENV : Package<"env">, ParentPackage; def Unix : Package<"unix">; def UnixAlpha : Package<"unix">, ParentPackage; @@ -932,11 +933,19 @@ def PutenvWithAuto : Checker<"34c">, HelpText<"Finds calls to the 'putenv' function which pass a pointer to " - "an automatic variable as the argument.">, + "an automatic variable as the argument">, Documentation; } // end "alpha.cert.pos" +let ParentPackage = ENV in { + + def InvalidPtrChecker : Checker<"InvalidPtr">, + HelpText<"Finds usages of possibly invalidated pointers">, + Documentation; + +} // end "alpha.cert.env" + let ParentPackage = SecurityAlpha in { def ArrayBoundChecker : Checker<"ArrayBound">, diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt --- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -39,6 +39,7 @@ DivZeroChecker.cpp DynamicTypePropagation.cpp DynamicTypeChecker.cpp + cert/InvalidPtrChecker.cpp EnumCastOutOfRangeChecker.cpp ExprInspectionChecker.cpp FixedAddressChecker.cpp diff --git a/clang/lib/StaticAnalyzer/Checkers/cert/InvalidPtrChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/cert/InvalidPtrChecker.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/cert/InvalidPtrChecker.cpp @@ -0,0 +1,382 @@ +//== InvalidPtrChecker.cpp ------------------------------------- -*- C++ -*--=// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines InvalidPtrChecker which finds usages of possibly +// invalidated pointer. +// CERT SEI Rules ENV31-C and ENV34-C +// For more information see: +// https://wiki.sei.cmu.edu/confluence/x/8tYxBQ +// https://wiki.sei.cmu.edu/confluence/x/5NUxBQ +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/ASTContext.h" +#include "clang/Analysis/AnalysisDeclContext.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SValVisitor.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" +#include "llvm/Support/ErrorHandling.h" +#include + +using namespace clang; +using namespace ento; + +namespace { + +class InvalidPtrChecker + : public Checker { +private: + BugType BT{this, "Use of invalidated pointer", categories::MemoryError}; + + void EnvpInvalidatingCall(const CallEvent &Call, CheckerContext &C) const; + + using HandlerFn = void (InvalidPtrChecker::*)(const CallEvent &Call, + CheckerContext &C) const; + + // SEI CERT ENV31-C + const CallDescriptionMap EnvpInvalidatingFunctions = { + {{"setenv", 3}, &InvalidPtrChecker::EnvpInvalidatingCall}, + {{"unsetenv", 1}, &InvalidPtrChecker::EnvpInvalidatingCall}, + {{"putenv", 1}, &InvalidPtrChecker::EnvpInvalidatingCall}, + {{"_putenv_s", 2}, &InvalidPtrChecker::EnvpInvalidatingCall}, + {{"_wputenv_s", 2}, &InvalidPtrChecker::EnvpInvalidatingCall}, + }; + + using EvalFn = bool (InvalidPtrChecker::*)(const CallEvent &Call, + CheckerContext &C) const; + + bool evalPreviousReturnInvalidatingCall(StringRef FunctionName, + const CallEvent &Call, + CheckerContext &C) const; + + bool evalGetenv(const CallEvent &Call, CheckerContext &C) const { + return evalPreviousReturnInvalidatingCall("getenv", Call, C); + } + + bool evalSetlocale(const CallEvent &Call, CheckerContext &C) const { + return evalPreviousReturnInvalidatingCall("setlocale", Call, C); + } + + bool evalStrerror(const CallEvent &Call, CheckerContext &C) const { + return evalPreviousReturnInvalidatingCall("strerror", Call, C); + } + + bool evalLocaleconv(const CallEvent &Call, CheckerContext &C) const { + return evalPreviousReturnInvalidatingCall("localeconv", Call, C); + } + + bool evalAsctime(const CallEvent &Call, CheckerContext &C) const { + return evalPreviousReturnInvalidatingCall("asctime", Call, C); + } + + // SEI CERT ENV34-C + const CallDescriptionMap PreviousCallInvalidatingFunctions = { + {{"getenv", 1}, &InvalidPtrChecker::evalGetenv}, + {{"setlocale", 2}, &InvalidPtrChecker::evalSetlocale}, + {{"strerror", 1}, &InvalidPtrChecker::evalStrerror}, + {{"localeconv", 0}, &InvalidPtrChecker::evalLocaleconv}, + {{"asctime", 1}, &InvalidPtrChecker::evalAsctime}, + }; + +public: + // Obtain the environment pointer from 'main()' (if present). + void checkBeginFunction(CheckerContext &C) const; + + // Handle functions in EnvpInvalidatingFunctions, that invalidate environment + // pointer from 'main()' Also, check if invalidated region is passed to a + // conservatively evaluated function call as an argument. + void checkPostCall(const CallEvent &Call, CheckerContext &C) const; + + // Handle functions in PreviousCallInvalidatingFunctions. + bool evalCall(const CallEvent &Call, CheckerContext &C) const; + + // Check if invalidated region is being dereferenced. + void checkLocation(SVal l, bool isLoad, const Stmt *S, + CheckerContext &C) const; + + void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; +}; + +class PreviousCallVisitor : public BugReporterVisitor { +public: + PreviousCallVisitor(const MemRegion *InvalidatedSymbolicBase) + : InvalidatedSymbolicBase(InvalidatedSymbolicBase) {} + void Profile(llvm::FoldingSetNodeID &ID) const override { + static int X = 0; + ID.AddPointer(&X); + } + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) override; + +private: + bool Satisfied = false; + const MemRegion *InvalidatedSymbolicBase; +}; + +} // namespace + +// Set of memory regions that were invalidated +REGISTER_SET_WITH_PROGRAMSTATE(InvalidMemoryRegions, const MemRegion *) + +// Stores the region of the environment pointer of 'main' (if present). +// Note: This pointer has type 'const MemRegion *' +REGISTER_TRAIT_WITH_PROGRAMSTATE(EnvPtrRegion, const void *) + +// Stores key-value pairs, where key is function name and value is pointer to +// memory region returned by previous call of this function +REGISTER_MAP_WITH_PROGRAMSTATE(PreviousCallResultMap, const char *, + const MemRegion *) + +bool InvalidPtrChecker::evalCall(const CallEvent &Call, + CheckerContext &C) const { + if (const auto *Handler = PreviousCallInvalidatingFunctions.lookup(Call)) + return (this->**Handler)(Call, C); + return false; +} + +void InvalidPtrChecker::EnvpInvalidatingCall(const CallEvent &Call, + CheckerContext &C) const { + StringRef FunctionName = Call.getCalleeIdentifier()->getName(); + ProgramStateRef State = C.getState(); + const auto *Reg = State->get(); + if (!Reg) + return; + const auto *SymbolicEnvPtrRegion = + reinterpret_cast(const_cast(Reg)); + + State = State->add(SymbolicEnvPtrRegion); + + const NoteTag *Note = + C.getNoteTag([SymbolicEnvPtrRegion, FunctionName]( + PathSensitiveBugReport &BR, llvm::raw_ostream &Out) { + if (!BR.isInteresting(SymbolicEnvPtrRegion)) + return; + Out << '\'' << FunctionName + << "' call may invalidate the environment parameter of 'main'"; + }); + + C.addTransition(State, Note); +} + +bool InvalidPtrChecker::evalPreviousReturnInvalidatingCall( + StringRef FunctionName, const CallEvent &Call, CheckerContext &C) const { + ProgramStateRef State = C.getState(); + + const NoteTag *Note = nullptr; + + // Invalidate the region of the previously returned pointer - if there was + // one. + if (const auto *Reg = + State->get(FunctionName.data())) { + const auto *PrevReg = *Reg; + State = State->add(PrevReg); + Note = C.getNoteTag([PrevReg, FunctionName](PathSensitiveBugReport &BR, + llvm::raw_ostream &Out) { + if (!BR.isInteresting(PrevReg)) + return; + Out << '\'' << FunctionName + << "' call may invalidate the the result of the previous " << '\'' + << FunctionName << '\''; + }); + } + + const LocationContext *LCtx = C.getLocationContext(); + const auto *CE = cast(Call.getOriginExpr()); + + // Function call will return a pointer to the new symbolic region. + DefinedOrUnknownSVal RetVal = C.getSValBuilder().conjureSymbolVal( + CE, LCtx, CE->getType(), C.blockCount()); + State = State->BindExpr(CE, LCtx, RetVal); + + // Remember to this region. + const auto *SymRegOfRetVal = dyn_cast(RetVal.getAsRegion()); + State = State->set( + FunctionName.data(), + const_cast(SymRegOfRetVal->getBaseRegion())); + + C.addTransition(State, Note); + return true; +} + +// FIXME: This seems really ugly. Simplify this. +static const MemRegion *findInvalidatedSymbolicBase(ProgramStateRef State, + const MemRegion *Reg) { + while (Reg) { + if (State->contains(Reg)) + return Reg; + const auto *SymBase = Reg->getSymbolicBase(); + if (!SymBase) + break; + const auto *SRV = dyn_cast(SymBase->getSymbol()); + if (!SRV) + break; + Reg = SRV->getRegion(); + if (const auto *VarReg = dyn_cast(SRV->getRegion())) + Reg = VarReg; + } + return nullptr; +} + +// Handle functions in EnvpInvalidatingFunctions, that invalidate environment +// pointer from 'main()' Also, check if invalidated region is passed to a +// function call as an argument. +void InvalidPtrChecker::checkPostCall(const CallEvent &Call, + CheckerContext &C) const { + // Check if function invalidates 'envp' argument of 'main' + if (const auto *Handler = EnvpInvalidatingFunctions.lookup(Call)) + (this->**Handler)(Call, C); + + // Check if one of the arguments of the function call is invalidated + + // If call was inlined, don't report invalidated argument + if (C.wasInlined) + return; + + ProgramStateRef State = C.getState(); + + for (unsigned I = 0, NumArgs = Call.getNumArgs(); I < NumArgs; ++I) { + + if (const auto *SR = dyn_cast_or_null( + Call.getArgSVal(I).getAsRegion())) { + if (const MemRegion *InvalidatedSymbolicBase = + findInvalidatedSymbolicBase(State, SR)) { + ExplodedNode *ErrorNode = C.generateNonFatalErrorNode(); + if (!ErrorNode) + return; + + SmallString<256> Msg; + llvm::raw_svector_ostream Out(Msg); + Out << "use of invalidated pointer '"; + Call.getArgExpr(I)->printPretty(Out, /*Helper=*/nullptr, + C.getASTContext().getPrintingPolicy()); + Out << "' in a function call"; + + auto Report = + std::make_unique(BT, Out.str(), ErrorNode); + Report->markInteresting(InvalidatedSymbolicBase); + Report->addRange(Call.getArgSourceRange(I)); + Report->addVisitor( + std::make_unique(InvalidatedSymbolicBase)); + + C.emitReport(std::move(Report)); + C.addTransition(State); + } + } + } +} + +// Obtain the environment pointer from 'main()', if present. +void InvalidPtrChecker::checkBeginFunction(CheckerContext &C) const { + if (!C.inTopFrame()) + return; + + const auto *FD = dyn_cast(C.getLocationContext()->getDecl()); + if (!FD || FD->param_size() != 3 || !FD->isMain()) + return; + + ProgramStateRef State = C.getState(); + const MemRegion *EnvpReg = + State->getRegion(FD->parameters()[2], C.getLocationContext()); + + // Save the memory region pointed by the environment pointer parameter of + // 'main'. + State = State->set( + reinterpret_cast(const_cast(EnvpReg))); + C.addTransition(State); +} + +// Check if invalidated region is being dereferenced. +void InvalidPtrChecker::checkLocation(SVal Loc, bool isLoad, const Stmt *S, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + + // Ignore memory operations involving 'non-invalidated' locations. + const MemRegion *InvalidatedSymbolicBase = + findInvalidatedSymbolicBase(State, Loc.getAsRegion()); + if (!InvalidatedSymbolicBase) + return; + + ExplodedNode *ErrorNode = C.generateNonFatalErrorNode(); + if (!ErrorNode) + return; + + auto Report = std::make_unique( + BT, "dereferencing an invalid pointer", ErrorNode); + Report->markInteresting(InvalidatedSymbolicBase); + Report->addVisitor( + std::make_unique(InvalidatedSymbolicBase)); + C.emitReport(std::move(Report)); +} + +void InvalidPtrChecker::checkDeadSymbols(SymbolReaper &SymReaper, + CheckerContext &C) const { + // TODO: some information is garbage collected too early. + + /*ProgramStateRef State = C.getState(); + InvalidMemoryRegionsTy TrackedRegions = State->get(); + for (auto E : TrackedRegions) { + if (!SymReaper.isLiveRegion(E)) { + State = State->remove(E); + } + } + C.addTransition(State);*/ +} + +PathDiagnosticPieceRef +PreviousCallVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &BRC, + PathSensitiveBugReport &BR) { + // Stop traversal if previous call was found already + if (Satisfied) + return nullptr; + + const Stmt *S = N->getStmtForDiagnostics(); + if (!S) + return nullptr; + + const auto *CallE = dyn_cast(S); + if (!CallE) + return nullptr; + + // Region associated with the current call expression. + const MemRegion *M = N->getSVal(CallE).getAsRegion(); + if (!M) + return nullptr; + + // Check if target region is one we want. + if (M != InvalidatedSymbolicBase) + return nullptr; + + // Stop traversal on this path. + Satisfied = true; + + SmallString<256> Buf; + llvm::raw_svector_ostream Out(Buf); + Out << "previous function call was here"; + PathDiagnosticLocation Pos(S, BRC.getSourceManager(), + N->getLocationContext()); + + return std::make_shared(Pos, Out.str(), true); +} + +void ento::registerInvalidPtrChecker(CheckerManager &Mgr) { + Mgr.registerChecker(); +} + +bool ento::shouldRegisterInvalidPtrChecker(const CheckerManager &) { + return true; +} diff --git a/clang/test/Analysis/cert/env31-c/_putenv_s.c b/clang/test/Analysis/cert/env31-c/_putenv_s.c new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/cert/env31-c/_putenv_s.c @@ -0,0 +1,43 @@ +// RUN: %clang_analyze_cc1 \ +// RUN: -analyzer-checker=alpha.security.cert.env.InvalidPtr\ +// RUN: -analyzer-output=text -verify -Wno-unused %s + +typedef int errno_t; + +errno_t _putenv_s( + const char *varname, + const char *value_string +); + +void fn_without_body(char** e); +void fn_with_body(char** e) {} + +void call_putenv(char ** e){ + _putenv_s((char*) "SOMETHING", (char*) "SOMETHINGELSE"); + // expected-note@-1 3 {{'_putenv_s' call may invalidate the environment parameter of 'main'}} + + *e; + // expected-warning@-1{{dereferencing an invalid pointer}} + // expected-note@-2{{dereferencing an invalid pointer}} +} + +int main(int argc, char **argv, char *envp[]) { + char ** e = envp; + + *e; // no-warning + e[0]; // no-warning + *envp; // no-warning + call_putenv(e); + //expected-note@-1 3 {{Calling 'call_putenv'}} + //expected-note@-2 2 {{Returning from 'call_putenv'}} + + *e; + // expected-warning@-1{{dereferencing an invalid pointer}} + // expected-note@-2{{dereferencing an invalid pointer}} + + fn_without_body(e); + // expected-warning@-1{{use of invalidated pointer 'e' in a function call}} + // expected-note@-2{{use of invalidated pointer 'e' in a function call}} + + fn_with_body(e); // no-warning +} diff --git a/clang/test/Analysis/cert/env31-c/_wputenv_s.c b/clang/test/Analysis/cert/env31-c/_wputenv_s.c new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/cert/env31-c/_wputenv_s.c @@ -0,0 +1,44 @@ +// RUN: %clang_analyze_cc1 \ +// RUN: -analyzer-checker=alpha.security.cert.env.InvalidPtr\ +// RUN: -analyzer-output=text -verify -Wno-unused %s + +typedef char wchar_t; +typedef int errno_t; + +errno_t _wputenv_s( + const wchar_t *varname, + const wchar_t *value_string +); + +void fn_without_body(char** e); +void fn_with_body(char** e) {} + +void call_putenv(char ** e){ + _wputenv_s((char*) "SOMETHING", (char*) "SOMETHINGELSE"); + // expected-note@-1 3 {{'_wputenv_s' call may invalidate the environment parameter of 'main'}} + + *e; + // expected-warning@-1{{dereferencing an invalid pointer}} + // expected-note@-2{{dereferencing an invalid pointer}} +} + +int main(int argc, char **argv, char *envp[]) { + char ** e = envp; + + *e; // no-warning + e[0]; // no-warning + *envp; // no-warning + call_putenv(e); + //expected-note@-1 3 {{Calling 'call_putenv'}} + //expected-note@-2 2 {{Returning from 'call_putenv'}} + + *e; + // expected-warning@-1{{dereferencing an invalid pointer}} + // expected-note@-2{{dereferencing an invalid pointer}} + + fn_without_body(e); + // expected-warning@-1{{use of invalidated pointer 'e' in a function call}} + // expected-note@-2{{use of invalidated pointer 'e' in a function call}} + + fn_with_body(e); // no-warning +} diff --git a/clang/test/Analysis/cert/env31-c/putenv.c b/clang/test/Analysis/cert/env31-c/putenv.c new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/cert/env31-c/putenv.c @@ -0,0 +1,38 @@ +// RUN: %clang_analyze_cc1 \ +// RUN: -analyzer-checker=alpha.security.cert.env.InvalidPtr\ +// RUN: -analyzer-output=text -verify -Wno-unused %s + +int putenv(char *string); + +void fn_without_body(char** e); + +void call_putenv(char ** e){ + putenv((char*) "SOMETHING=SOMETHINGELSE"); + // expected-note@-1 3 {{'putenv' call may invalidate the environment parameter of 'main'}} + + *e; + // expected-warning@-1{{dereferencing an invalid pointer}} + // expected-note@-2{{dereferencing an invalid pointer}} +} + +int main(int argc, char **argv, char *envp[]) { + char ** e = envp; + + *e; // no-warning + e[0]; // no-warning + *envp; // no-warning + call_putenv(e); + //expected-note@-1 3 {{Calling 'call_putenv'}} + //expected-note@-2 2 {{Returning from 'call_putenv'}} + + putenv((char*) "SOMETHING=SOMETHINGELSE"); + // expected-note@-1 2 {{'putenv' call may invalidate the environment parameter of 'main'}} + + *e; + // expected-warning@-1{{dereferencing an invalid pointer}} + // expected-note@-2{{dereferencing an invalid pointer}} + + fn_with_body(e); + // expected-warning@-1{{use of invalidated pointer 'e' in a function call}} + // expected-note@-2{{use of invalidated pointer 'e' in a function call}} +} diff --git a/clang/test/Analysis/cert/env31-c/setenv.c b/clang/test/Analysis/cert/env31-c/setenv.c new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/cert/env31-c/setenv.c @@ -0,0 +1,35 @@ +// RUN: %clang_analyze_cc1 \ +// RUN: -analyzer-checker=alpha.security.cert.env.InvalidPtr\ +// RUN: -analyzer-output=text -verify -Wno-unused %s + +int setenv(const char *name, const char *value, int overwrite); + +void fn_without_body(char** e); + +void call_setenv(char ** e){ + setenv("name", "value", 0); + // expected-note@-1 3 {{'setenv' call may invalidate the environment parameter of 'main'}} + + *e; + // expected-warning@-1{{dereferencing an invalid pointer}} + // expected-note@-2{{dereferencing an invalid pointer}} +} + +int main(int argc, char **argv, char *envp[]) { + char ** e = envp; + + *e; // no-warning + e[0]; // no-warning + *envp; // no-warning + call_setenv(e); + //expected-note@-1 3 {{Calling 'call_setenv'}} + //expected-note@-2 2 {{Returning from 'call_setenv'}} + + *e; + // expected-warning@-1{{dereferencing an invalid pointer}} + // expected-note@-2{{dereferencing an invalid pointer}} + + fn_without_body(e); + // expected-warning@-1{{use of invalidated pointer 'e' in a function call}} + // expected-note@-2{{use of invalidated pointer 'e' in a function call}} +} diff --git a/clang/test/Analysis/cert/env31-c/unsetenv.c b/clang/test/Analysis/cert/env31-c/unsetenv.c new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/cert/env31-c/unsetenv.c @@ -0,0 +1,34 @@ +// RUN: %clang_analyze_cc1 \ +// RUN: -analyzer-checker=alpha.security.cert.env.InvalidPtr \ +// RUN: -analyzer-output=text -verify -Wno-unused %s + +int unsetenv(const char *name); + +void fn_without_body(char** e); + +void call_unsetenv(char ** e){ + unsetenv("name"); + // expected-note@-1 3 {{'unsetenv' call may invalidate the environment parameter of 'main'}} + + *e; + // expected-warning@-1{{dereferencing an invalid pointer}} + // expected-note@-2{{dereferencing an invalid pointer}} +} + +int main(int argc, char **argv, char *envp[]) { + char ** e = envp; + + *e; // no-warning + e[0]; // no-warning + *envp; // no-warning + call_unsetenv(e); + //expected-note@-1 3 {{Calling 'call_unsetenv'}} + //expected-note@-2 2 {{Returning from 'call_unsetenv'}} + *e; + // expected-warning@-1{{dereferencing an invalid pointer}} + // expected-note@-2{{dereferencing an invalid pointer}} + + fn_without_body(e); + // expected-warning@-1{{use of invalidated pointer 'e' in a function call}} + // expected-note@-2{{use of invalidated pointer 'e' in a function call}} +} diff --git a/clang/test/Analysis/cert/env34-c-cert-examples.c b/clang/test/Analysis/cert/env34-c-cert-examples.c new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/cert/env34-c-cert-examples.c @@ -0,0 +1,101 @@ +// RUN: %clang_analyze_cc1 \ +// RUN: -analyzer-checker=alpha.security.cert.env.InvalidPtr\ +// RUN: -verify -Wno-unused %s + +#include "../Inputs/system-header-simulator.h" +char *getenv(const char *name); +int strcmp(const char*, const char*); +char *strdup(const char*); +void free(void *memblock); +void *malloc(size_t size); + +void incorrect_usage(void) { + char *tmpvar; + char *tempvar; + + tmpvar = getenv("TMP"); + + if (!tmpvar) + return; + + tempvar = getenv("TEMP"); + + if (!tempvar) + return; + + if (strcmp(tmpvar, tempvar) == 0) { // body of strcmp is unknown + // expected-warning@-1{{use of invalidated pointer 'tmpvar' in a function call}} + } +} + +void correct_usage_1(void) { + char *tmpvar; + char *tempvar; + + const char *temp = getenv("TMP"); + if (temp != NULL) { + tmpvar = (char *)malloc(strlen(temp)+1); + if (tmpvar != NULL) { + strcpy(tmpvar, temp); + } else { + return; + } + } else { + return; + } + + temp = getenv("TEMP"); + if (temp != NULL) { + tempvar = (char *)malloc(strlen(temp)+1); + if (tempvar != NULL) { + strcpy(tempvar, temp); + } else { + return; + } + } else { + return; + } + + if (strcmp(tmpvar, tempvar) == 0) { + printf("TMP and TEMP are the same.\n"); + } else { + printf("TMP and TEMP are NOT the same.\n"); + } + free(tmpvar); + free(tempvar); +} + +void correct_usage_2(void) { + char *tmpvar; + char *tempvar; + + const char *temp = getenv("TMP"); + if (temp != NULL) { + tmpvar = strdup(temp); + if (tmpvar == NULL) { + return; + } + } else { + return; + } + + temp = getenv("TEMP"); + if (temp != NULL) { + tempvar = strdup(temp); + if (tempvar == NULL) { + return; + } + } else { + return; + } + + if (strcmp(tmpvar, tempvar) == 0) { + printf("TMP and TEMP are the same.\n"); + } else { + printf("TMP and TEMP are NOT the same.\n"); + } + free(tmpvar); + tmpvar = NULL; + free(tempvar); + tempvar = NULL; +} diff --git a/clang/test/Analysis/cert/env34-c.c b/clang/test/Analysis/cert/env34-c.c new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/cert/env34-c.c @@ -0,0 +1,241 @@ +// RUN: %clang_analyze_cc1 \ +// RUN: -analyzer-checker=alpha.security.cert.env.InvalidPtr\ +// RUN: -analyzer-output=text -verify -Wno-unused %s + +#include "../Inputs/system-header-simulator.h" +char *getenv(const char *name); +char *setlocale(int category, const char *locale); +char *strerror(int errnum); + +typedef struct { + char * field; +} lconv; +lconv *localeconv(void); + +typedef struct { +} tm; +char *asctime(const tm *timeptr); + +int strcmp(const char*, const char*); +extern void foo(char *e); +extern char* bar(); + + +void getenv_test1() { + char *p, *p2, *p3; + + p = getenv("VAR"); + *p; // no-warning + + p = getenv("VAR2"); + // expected-note@-1{{previous function call was here}} + p3 = getenv("VAR2"); + // expected-note@-1{{'getenv' call may invalidate the the result of the previous 'getenv'}} + + p2 = getenv("VAR3"); + + *p; + // expected-warning@-1{{dereferencing an invalid pointer}} + // expected-note@-2{{dereferencing an invalid pointer}} +} + +void getenv_test2() { + char *p, *p2; + p = getenv("VAR"); + *p; // no-warning + + p = getenv("VAR2"); + // expected-note@-1{{previous function call was here}} + *p; // no-warning + + p2 = getenv("VAR3"); + // expected-note@-1{{previous function call was here}} + // expected-note@-2{{'getenv' call may invalidate the the result of the previous 'getenv'}} + + *p; + // expected-warning@-1{{dereferencing an invalid pointer}} + // expected-note@-2{{dereferencing an invalid pointer}} + + *p2; // no-warning + + p = getenv("VAR4"); + // expected-note@-1{{'getenv' call may invalidate the the result of the previous 'getenv'}} + + *p; // no-warning + *p2; + // expected-warning@-1{{dereferencing an invalid pointer}} + // expected-note@-2{{dereferencing an invalid pointer}} +} + +void getenv_test3() { + char *p, *p2; + p = getenv("VAR"); + // expected-note@-1{{previous function call was here}} + *p; // no-warning + + p2 = getenv("VAR2"); + // expected-note@-1{{'getenv' call may invalidate the the result of the previous 'getenv'}} + + foo(p); + // expected-warning@-1{{use of invalidated pointer 'p' in a function call}} + // expected-note@-2{{use of invalidated pointer 'p' in a function call}} +} + +void getenv_test4() { + static const char *array[] = { + 0, + 0, + "/var/tmp", + "/usr/tmp", + "/tmp", + "." + }; + + if( !array[0] ) + // expected-note@-1{{Taking true branch}} + array[0] = getenv("TEMPDIR"); + // expected-note@-1{{previous function call was here}} + + if( !array[1] ) + // expected-note@-1{{Taking true branch}} + array[1] = getenv("TMPDIR"); + // expected-note@-1{{'getenv' call may invalidate the the result of the previous 'getenv'}} + + *array[0]; + // expected-warning@-1{{dereferencing an invalid pointer}} + // expected-note@-2{{dereferencing an invalid pointer}} +} + +void getenv_test5() { + char *p, *p2; + p = getenv("something"); + p = bar(); + p2 = getenv("something"); + *p; // no-warning +} + +void getenv_test6() { + strcmp(getenv("VAR1"), getenv("VAR2")); + // expected-note@-1{{'getenv' call may invalidate the the result of the previous 'getenv'}} + // expected-note@-2{{previous function call was here}} + // expected-warning@-3{{use of invalidated pointer 'getenv("VAR1")' in a function call}} + // expected-note@-4{{use of invalidated pointer 'getenv("VAR1")' in a function call}} +} + +void setlocale_test1() { + char *p, *p2; + p = setlocale(0, "VAR"); + *p; // no-warning + + p = setlocale(0, "VAR2"); + // expected-note@-1{{previous function call was here}} + *p; // no-warning + + p2 = setlocale(0, "VAR3"); + // expected-note@-1{{'setlocale' call may invalidate the the result of the previous 'setlocale'}} + + *p; + // expected-warning@-1{{dereferencing an invalid pointer}} + // expected-note@-2{{dereferencing an invalid pointer}} +} + +void setlocale_test2(int flag) { + char *p, *p2; + p = setlocale(0, "VAR"); + *p; // no-warning + + p = setlocale(0, "VAR2"); + // expected-note@-1{{previous function call was here}} + *p; // no-warning + + if (flag) { + // expected-note@-1{{Assuming 'flag' is not equal to 0}} + // expected-note@-2{{Taking true branch}} + p2 = setlocale(0, "VAR3"); + // expected-note@-1{{'setlocale' call may invalidate the the result of the previous 'setlocale'}} + } + + *p; + // expected-warning@-1{{dereferencing an invalid pointer}} + // expected-note@-2{{dereferencing an invalid pointer}} +} + +void strerror_test1() { + char *p, *p2; + + p = strerror(0); + *p; // no-warning + + p = strerror(1); + // expected-note@-1{{previous function call was here}} + *p; // no-warning + + p2 = strerror(2); + // expected-note@-1{{'strerror' call may invalidate the the result of the previous 'strerror'}} + + *p; + // expected-warning@-1{{dereferencing an invalid pointer}} + // expected-note@-2{{dereferencing an invalid pointer}} +} + +void strerror_test2(int errno) { + char *p, *p2; + + p = strerror(0); + *p; // no-warning + + p = strerror(1); + // expected-note@-1{{previous function call was here}} + *p; // no-warning + + if (0 == 1) { + // expected-note@-1{{0 is not equal to 1}} + // expected-note@-2{{Taking false branch}} + p2 = strerror(2); + } + + *p; // no-warning + + if (errno) { + // expected-note@-1{{Assuming 'errno' is not equal to 0}} + // expected-note@-2{{Taking true branch}} + p2 = strerror(errno); + // expected-note@-1{{'strerror' call may invalidate the the result of the previous 'strerror'}} + } + + *p; + // expected-warning@-1{{dereferencing an invalid pointer}} + // expected-note@-2{{dereferencing an invalid pointer}} +} + +void asctime_test() { + const tm *t; + const tm *tt; + + char* p = asctime(t); + // expected-note@-1{{previous function call was here}} + char* pp = asctime(tt); + // expected-note@-1{{'asctime' call may invalidate the the result of the previous 'asctime'}} + + *p; + // expected-warning@-1{{dereferencing an invalid pointer}} + // expected-note@-2{{dereferencing an invalid pointer}} +} + +void localeconv_test1() { + lconv *lc1 = localeconv(); + // expected-note@-1{{previous function call was here}} + lconv *lc2 = localeconv(); + // expected-note@-1{{'localeconv' call may invalidate the the result of the previous 'localeconv'}} + + *lc1; + // expected-warning@-1{{dereferencing an invalid pointer}} + // expected-note@-2{{dereferencing an invalid pointer}} +} + +void localeconv_test2() { + // FIXME: false negative + lconv *lc1 = localeconv(); + lconv *lc2 = localeconv(); + lc1->field; +}