Skip to content

Commit 811b173

Browse files
author
Zurab Tsinadze
committed
[analyzer] Add InvalidPtrChecker
This patch introduces a new checker: `alpha.security.cert.env.InvalidPtr` Checker finds usage of invalidated pointers related to environment. Based on the following SEI CERT Rules: ENV34-C: https://wiki.sei.cmu.edu/confluence/x/8tYxBQ ENV31-C: https://wiki.sei.cmu.edu/confluence/x/5NUxBQ Reviewed By: martong Differential Revision: https://reviews.llvm.org/D97699
1 parent c63a9a7 commit 811b173

File tree

7 files changed

+849
-2
lines changed

7 files changed

+849
-2
lines changed

clang/docs/analyzer/checkers.rst

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2075,10 +2075,63 @@ Finds calls to the ``putenv`` function which pass a pointer to an automatic vari
20752075
if (retval < 0 || (size_t)retval >= sizeof(env)) {
20762076
/* Handle error */
20772077
}
2078-
2078+
20792079
return putenv(env); // putenv function should not be called with auto variables
20802080
}
2081-
2081+
2082+
alpha.security.cert.env
2083+
^^^^^^^^^^^^^^^^^^^^^^^
2084+
2085+
SEI CERT checkers of `POSIX C coding rules <https://wiki.sei.cmu.edu/confluence/x/JdcxBQ>`_.
2086+
2087+
.. _alpha-security-cert-env-InvalidPtr:
2088+
2089+
alpha.security.cert.env.InvalidPtr
2090+
"""""""""""""""""""""""""""
2091+
2092+
Corresponds to SEI CERT Rules ENV31-C and ENV34-C.
2093+
2094+
ENV31-C:
2095+
Rule is about the possible problem with `main` function's third argument, environment pointer,
2096+
"envp". When enviornment array is modified using some modification function
2097+
such as putenv, setenv or others, It may happen that memory is reallocated,
2098+
however "envp" is not updated to reflect the changes and points to old memory
2099+
region.
2100+
2101+
ENV34-C:
2102+
Some functions return a pointer to a statically allocated buffer.
2103+
Consequently, subsequent call of these functions will invalidate previous
2104+
pointer. These functions include: getenv, localeconv, asctime, setlocale, strerror
2105+
2106+
.. code-block:: c
2107+
2108+
int main(int argc, const char *argv[], const char *envp[]) {
2109+
if (setenv("MY_NEW_VAR", "new_value", 1) != 0) {
2110+
// setenv call may invalidate 'envp'
2111+
/* Handle error */
2112+
}
2113+
if (envp != NULL) {
2114+
for (size_t i = 0; envp[i] != NULL; ++i) {
2115+
puts(envp[i]);
2116+
// envp may no longer point to the current environment
2117+
// this program has unanticipated behavior, since envp
2118+
// does not reflect changes made by setenv function.
2119+
}
2120+
}
2121+
return 0;
2122+
}
2123+
2124+
void previous_call_invalidation() {
2125+
char *p, *pp;
2126+
2127+
p = getenv("VAR");
2128+
pp = getenv("VAR2");
2129+
// subsequent call to 'getenv' invalidated previous one
2130+
2131+
*p;
2132+
// dereferencing invalid pointer
2133+
}
2134+
20822135
.. _alpha-security-ArrayBound:
20832136
20842137
alpha.security.ArrayBound (C)

clang/include/clang/StaticAnalyzer/Checkers/Checkers.td

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ def Taint : Package<"taint">, ParentPackage<SecurityAlpha>;
7373

7474
def CERT : Package<"cert">, ParentPackage<SecurityAlpha>;
7575
def POS : Package<"pos">, ParentPackage<CERT>;
76+
def ENV : Package<"env">, ParentPackage<CERT>;
7677

7778
def Unix : Package<"unix">;
7879
def UnixAlpha : Package<"unix">, ParentPackage<Alpha>;
@@ -947,6 +948,14 @@ let ParentPackage = POS in {
947948

948949
} // end "alpha.cert.pos"
949950

951+
let ParentPackage = ENV in {
952+
953+
def InvalidPtrChecker : Checker<"InvalidPtr">,
954+
HelpText<"Finds usages of possibly invalidated pointers">,
955+
Documentation<HasDocumentation>;
956+
957+
} // end "alpha.cert.env"
958+
950959
let ParentPackage = SecurityAlpha in {
951960

952961
def ArrayBoundChecker : Checker<"ArrayBound">,

clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ add_clang_library(clangStaticAnalyzerCheckers
4949
IdenticalExprChecker.cpp
5050
InnerPointerChecker.cpp
5151
InvalidatedIteratorChecker.cpp
52+
cert/InvalidPtrChecker.cpp
5253
Iterator.cpp
5354
IteratorModeling.cpp
5455
IteratorRangeChecker.cpp
Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
//== InvalidPtrChecker.cpp ------------------------------------- -*- C++ -*--=//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// This file defines InvalidPtrChecker which finds usages of possibly
10+
// invalidated pointer.
11+
// CERT SEI Rules ENV31-C and ENV34-C
12+
// For more information see:
13+
// https://wiki.sei.cmu.edu/confluence/x/8tYxBQ
14+
// https://wiki.sei.cmu.edu/confluence/x/5NUxBQ
15+
//===----------------------------------------------------------------------===//
16+
17+
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
18+
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
19+
#include "clang/StaticAnalyzer/Core/Checker.h"
20+
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
21+
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
22+
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
23+
24+
using namespace clang;
25+
using namespace ento;
26+
27+
namespace {
28+
29+
class InvalidPtrChecker
30+
: public Checker<check::Location, check::BeginFunction, check::PostCall> {
31+
private:
32+
BugType BT{this, "Use of invalidated pointer", categories::MemoryError};
33+
34+
void EnvpInvalidatingCall(const CallEvent &Call, CheckerContext &C) const;
35+
36+
using HandlerFn = void (InvalidPtrChecker::*)(const CallEvent &Call,
37+
CheckerContext &C) const;
38+
39+
// SEI CERT ENV31-C
40+
const CallDescriptionMap<HandlerFn> EnvpInvalidatingFunctions = {
41+
{{"setenv", 3}, &InvalidPtrChecker::EnvpInvalidatingCall},
42+
{{"unsetenv", 1}, &InvalidPtrChecker::EnvpInvalidatingCall},
43+
{{"putenv", 1}, &InvalidPtrChecker::EnvpInvalidatingCall},
44+
{{"_putenv_s", 2}, &InvalidPtrChecker::EnvpInvalidatingCall},
45+
{{"_wputenv_s", 2}, &InvalidPtrChecker::EnvpInvalidatingCall},
46+
};
47+
48+
void postPreviousReturnInvalidatingCall(const CallEvent &Call,
49+
CheckerContext &C) const;
50+
51+
// SEI CERT ENV34-C
52+
const CallDescriptionMap<HandlerFn> PreviousCallInvalidatingFunctions = {
53+
{{"getenv", 1}, &InvalidPtrChecker::postPreviousReturnInvalidatingCall},
54+
{{"setlocale", 2},
55+
&InvalidPtrChecker::postPreviousReturnInvalidatingCall},
56+
{{"strerror", 1}, &InvalidPtrChecker::postPreviousReturnInvalidatingCall},
57+
{{"localeconv", 0},
58+
&InvalidPtrChecker::postPreviousReturnInvalidatingCall},
59+
{{"asctime", 1}, &InvalidPtrChecker::postPreviousReturnInvalidatingCall},
60+
};
61+
62+
public:
63+
// Obtain the environment pointer from 'main()' (if present).
64+
void checkBeginFunction(CheckerContext &C) const;
65+
66+
// Handle functions in EnvpInvalidatingFunctions, that invalidate environment
67+
// pointer from 'main()'
68+
// Handle functions in PreviousCallInvalidatingFunctions.
69+
// Also, check if invalidated region is passed to a
70+
// conservatively evaluated function call as an argument.
71+
void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
72+
73+
// Check if invalidated region is being dereferenced.
74+
void checkLocation(SVal l, bool isLoad, const Stmt *S,
75+
CheckerContext &C) const;
76+
};
77+
78+
} // namespace
79+
80+
// Set of memory regions that were invalidated
81+
REGISTER_SET_WITH_PROGRAMSTATE(InvalidMemoryRegions, const MemRegion *)
82+
83+
// Stores the region of the environment pointer of 'main' (if present).
84+
// Note: This pointer has type 'const MemRegion *', however the trait is only
85+
// specialized to 'const void*' and 'void*'
86+
REGISTER_TRAIT_WITH_PROGRAMSTATE(EnvPtrRegion, const void *)
87+
88+
// Stores key-value pairs, where key is function declaration and value is
89+
// pointer to memory region returned by previous call of this function
90+
REGISTER_MAP_WITH_PROGRAMSTATE(PreviousCallResultMap, const FunctionDecl *,
91+
const MemRegion *)
92+
93+
void InvalidPtrChecker::EnvpInvalidatingCall(const CallEvent &Call,
94+
CheckerContext &C) const {
95+
StringRef FunctionName = Call.getCalleeIdentifier()->getName();
96+
ProgramStateRef State = C.getState();
97+
const auto *Reg = State->get<EnvPtrRegion>();
98+
if (!Reg)
99+
return;
100+
const auto *SymbolicEnvPtrRegion =
101+
reinterpret_cast<const MemRegion *>(const_cast<const void *>(Reg));
102+
103+
State = State->add<InvalidMemoryRegions>(SymbolicEnvPtrRegion);
104+
105+
const NoteTag *Note =
106+
C.getNoteTag([SymbolicEnvPtrRegion, FunctionName](
107+
PathSensitiveBugReport &BR, llvm::raw_ostream &Out) {
108+
if (!BR.isInteresting(SymbolicEnvPtrRegion))
109+
return;
110+
Out << '\'' << FunctionName
111+
<< "' call may invalidate the environment parameter of 'main'";
112+
});
113+
114+
C.addTransition(State, Note);
115+
}
116+
117+
void InvalidPtrChecker::postPreviousReturnInvalidatingCall(
118+
const CallEvent &Call, CheckerContext &C) const {
119+
ProgramStateRef State = C.getState();
120+
121+
const NoteTag *Note = nullptr;
122+
const FunctionDecl *FD = dyn_cast_or_null<FunctionDecl>(Call.getDecl());
123+
// Invalidate the region of the previously returned pointer - if there was
124+
// one.
125+
if (const MemRegion *const *Reg = State->get<PreviousCallResultMap>(FD)) {
126+
const MemRegion *PrevReg = *Reg;
127+
State = State->add<InvalidMemoryRegions>(PrevReg);
128+
Note = C.getNoteTag([PrevReg, FD](PathSensitiveBugReport &BR,
129+
llvm::raw_ostream &Out) {
130+
if (!BR.isInteresting(PrevReg))
131+
return;
132+
Out << '\'';
133+
FD->getNameForDiagnostic(Out, FD->getASTContext().getLangOpts(), true);
134+
Out << "' call may invalidate the the result of the previous " << '\'';
135+
FD->getNameForDiagnostic(Out, FD->getASTContext().getLangOpts(), true);
136+
Out << '\'';
137+
});
138+
}
139+
140+
const LocationContext *LCtx = C.getLocationContext();
141+
const auto *CE = cast<CallExpr>(Call.getOriginExpr());
142+
143+
// Function call will return a pointer to the new symbolic region.
144+
DefinedOrUnknownSVal RetVal = C.getSValBuilder().conjureSymbolVal(
145+
CE, LCtx, CE->getType(), C.blockCount());
146+
State = State->BindExpr(CE, LCtx, RetVal);
147+
148+
// Remember to this region.
149+
const auto *SymRegOfRetVal = dyn_cast<SymbolicRegion>(RetVal.getAsRegion());
150+
const MemRegion *MR =
151+
const_cast<MemRegion *>(SymRegOfRetVal->getBaseRegion());
152+
State = State->set<PreviousCallResultMap>(FD, MR);
153+
154+
ExplodedNode *Node = C.addTransition(State, Note);
155+
const NoteTag *PreviousCallNote =
156+
C.getNoteTag([MR](PathSensitiveBugReport &BR, llvm::raw_ostream &Out) {
157+
if (!BR.isInteresting(MR))
158+
return;
159+
Out << '\'' << "'previous function call was here" << '\'';
160+
});
161+
162+
C.addTransition(State, Node, PreviousCallNote);
163+
}
164+
165+
// TODO: This seems really ugly. Simplify this.
166+
static const MemRegion *findInvalidatedSymbolicBase(ProgramStateRef State,
167+
const MemRegion *Reg) {
168+
while (Reg) {
169+
if (State->contains<InvalidMemoryRegions>(Reg))
170+
return Reg;
171+
const auto *SymBase = Reg->getSymbolicBase();
172+
if (!SymBase)
173+
break;
174+
const auto *SRV = dyn_cast<SymbolRegionValue>(SymBase->getSymbol());
175+
if (!SRV)
176+
break;
177+
Reg = SRV->getRegion();
178+
if (const auto *VarReg = dyn_cast<VarRegion>(SRV->getRegion()))
179+
Reg = VarReg;
180+
}
181+
return nullptr;
182+
}
183+
184+
// Handle functions in EnvpInvalidatingFunctions, that invalidate environment
185+
// pointer from 'main()' Also, check if invalidated region is passed to a
186+
// function call as an argument.
187+
void InvalidPtrChecker::checkPostCall(const CallEvent &Call,
188+
CheckerContext &C) const {
189+
// Check if function invalidates 'envp' argument of 'main'
190+
if (const auto *Handler = EnvpInvalidatingFunctions.lookup(Call))
191+
(this->**Handler)(Call, C);
192+
193+
// Check if function invalidates the result of previous call
194+
if (const auto *Handler = PreviousCallInvalidatingFunctions.lookup(Call))
195+
(this->**Handler)(Call, C);
196+
197+
// Check if one of the arguments of the function call is invalidated
198+
199+
// If call was inlined, don't report invalidated argument
200+
if (C.wasInlined)
201+
return;
202+
203+
ProgramStateRef State = C.getState();
204+
205+
for (unsigned I = 0, NumArgs = Call.getNumArgs(); I < NumArgs; ++I) {
206+
207+
if (const auto *SR = dyn_cast_or_null<SymbolicRegion>(
208+
Call.getArgSVal(I).getAsRegion())) {
209+
if (const MemRegion *InvalidatedSymbolicBase =
210+
findInvalidatedSymbolicBase(State, SR)) {
211+
ExplodedNode *ErrorNode = C.generateNonFatalErrorNode();
212+
if (!ErrorNode)
213+
return;
214+
215+
SmallString<256> Msg;
216+
llvm::raw_svector_ostream Out(Msg);
217+
Out << "use of invalidated pointer '";
218+
Call.getArgExpr(I)->printPretty(Out, /*Helper=*/nullptr,
219+
C.getASTContext().getPrintingPolicy());
220+
Out << "' in a function call";
221+
222+
auto Report =
223+
std::make_unique<PathSensitiveBugReport>(BT, Out.str(), ErrorNode);
224+
Report->markInteresting(InvalidatedSymbolicBase);
225+
Report->addRange(Call.getArgSourceRange(I));
226+
C.emitReport(std::move(Report));
227+
}
228+
}
229+
}
230+
}
231+
232+
// Obtain the environment pointer from 'main()', if present.
233+
void InvalidPtrChecker::checkBeginFunction(CheckerContext &C) const {
234+
if (!C.inTopFrame())
235+
return;
236+
237+
const auto *FD = dyn_cast<FunctionDecl>(C.getLocationContext()->getDecl());
238+
if (!FD || FD->param_size() != 3 || !FD->isMain())
239+
return;
240+
241+
ProgramStateRef State = C.getState();
242+
const MemRegion *EnvpReg =
243+
State->getRegion(FD->parameters()[2], C.getLocationContext());
244+
245+
// Save the memory region pointed by the environment pointer parameter of
246+
// 'main'.
247+
State = State->set<EnvPtrRegion>(
248+
reinterpret_cast<void *>(const_cast<MemRegion *>(EnvpReg)));
249+
C.addTransition(State);
250+
}
251+
252+
// Check if invalidated region is being dereferenced.
253+
void InvalidPtrChecker::checkLocation(SVal Loc, bool isLoad, const Stmt *S,
254+
CheckerContext &C) const {
255+
ProgramStateRef State = C.getState();
256+
257+
// Ignore memory operations involving 'non-invalidated' locations.
258+
const MemRegion *InvalidatedSymbolicBase =
259+
findInvalidatedSymbolicBase(State, Loc.getAsRegion());
260+
if (!InvalidatedSymbolicBase)
261+
return;
262+
263+
ExplodedNode *ErrorNode = C.generateNonFatalErrorNode();
264+
if (!ErrorNode)
265+
return;
266+
267+
auto Report = std::make_unique<PathSensitiveBugReport>(
268+
BT, "dereferencing an invalid pointer", ErrorNode);
269+
Report->markInteresting(InvalidatedSymbolicBase);
270+
C.emitReport(std::move(Report));
271+
}
272+
273+
void ento::registerInvalidPtrChecker(CheckerManager &Mgr) {
274+
Mgr.registerChecker<InvalidPtrChecker>();
275+
}
276+
277+
bool ento::shouldRegisterInvalidPtrChecker(const CheckerManager &) {
278+
return true;
279+
}

0 commit comments

Comments
 (0)