Skip to content

[analyzer][clangsa] Add new option to alpha.security.cert.InvalidPtrChecker #67663

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Oct 24, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions clang/docs/analyzer/checkers.rst
Original file line number Diff line number Diff line change
@@ -2520,13 +2520,34 @@ pointer. These functions include: getenv, localeconv, asctime, setlocale, strerr
char *p, *pp;

p = getenv("VAR");
pp = getenv("VAR2");
// subsequent call to 'getenv' invalidated previous one
setenv("SOMEVAR", "VALUE", /*overwrite = */1);
// call to 'setenv' may invalidate p

*p;
// dereferencing invalid pointer
}


The ``InvalidatingGetEnv`` option is available for treating getenv calls as
invalidating. When enabled, the checker issues a warning if getenv is called
multiple times and their results are used without first creating a copy.
This level of strictness might be considered overly pedantic for the commonly
used getenv implementations.

To enable this option, use:
``-analyzer-config alpha.security.cert.env.InvalidPtr:InvalidatingGetEnv=true``.

By default, this option is set to *false*.

When this option is enabled, warnings will be generated for scenarios like the
following:

.. code-block:: c

char* p = getenv("VAR");
char* pp = getenv("VAR2"); // assumes this call can invalidate `env`
strlen(p); // warns about accessing invalid ptr

alpha.security.taint
^^^^^^^^^^^^^^^^^^^^

9 changes: 9 additions & 0 deletions clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
Original file line number Diff line number Diff line change
@@ -1002,6 +1002,15 @@ let ParentPackage = ENV in {

def InvalidPtrChecker : Checker<"InvalidPtr">,
HelpText<"Finds usages of possibly invalidated pointers">,
CheckerOptions<[
CmdLineOption<Boolean,
"InvalidatingGetEnv",
"Regard getenv as an invalidating call (as per POSIX "
"standard), which can lead to false positives depending on "
"implementation.",
"false",
InAlpha>,
]>,
Documentation<HasDocumentation>;

} // end "alpha.cert.env"
145 changes: 111 additions & 34 deletions clang/lib/StaticAnalyzer/Checkers/cert/InvalidPtrChecker.cpp
Original file line number Diff line number Diff line change
@@ -30,14 +30,26 @@ namespace {
class InvalidPtrChecker
: public Checker<check::Location, check::BeginFunction, check::PostCall> {
private:
BugType BT{this, "Use of invalidated pointer", categories::MemoryError};
// For accurate emission of NoteTags, the BugType of this checker should have
// a unique address.
BugType InvalidPtrBugType{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

// If set to true, consider getenv calls as invalidating operations on the
// environment variable buffer. This is implied in the standard, but in
// practice does not cause problems (in the commonly used environments).
bool InvalidatingGetEnv = false;

// GetEnv can be treated invalidating and non-invalidating as well.
const CallDescription GetEnvCall{{"getenv"}, 1};

const CallDescriptionMap<HandlerFn> EnvpInvalidatingFunctions = {
{{{"setenv"}, 3}, &InvalidPtrChecker::EnvpInvalidatingCall},
{{{"unsetenv"}, 1}, &InvalidPtrChecker::EnvpInvalidatingCall},
@@ -51,7 +63,6 @@ class InvalidPtrChecker

// SEI CERT ENV34-C
const CallDescriptionMap<HandlerFn> PreviousCallInvalidatingFunctions = {
{{{"getenv"}, 1}, &InvalidPtrChecker::postPreviousReturnInvalidatingCall},
{{{"setlocale"}, 2},
&InvalidPtrChecker::postPreviousReturnInvalidatingCall},
{{{"strerror"}, 1},
@@ -62,6 +73,10 @@ class InvalidPtrChecker
&InvalidPtrChecker::postPreviousReturnInvalidatingCall},
};

// The private members of this checker corresponding to commandline options
// are set in this function.
friend void ento::registerInvalidPtrChecker(CheckerManager &);

public:
// Obtain the environment pointer from 'main()' (if present).
void checkBeginFunction(CheckerContext &C) const;
@@ -76,6 +91,11 @@ class InvalidPtrChecker
// Check if invalidated region is being dereferenced.
void checkLocation(SVal l, bool isLoad, const Stmt *S,
CheckerContext &C) const;

private:
const NoteTag *createEnvInvalidationNote(CheckerContext &C,
ProgramStateRef State,
StringRef FunctionName) const;
};

} // namespace
@@ -84,33 +104,74 @@ class InvalidPtrChecker
REGISTER_SET_WITH_PROGRAMSTATE(InvalidMemoryRegions, const MemRegion *)

// Stores the region of the environment pointer of 'main' (if present).
REGISTER_TRAIT_WITH_PROGRAMSTATE(EnvPtrRegion, const MemRegion *)
REGISTER_TRAIT_WITH_PROGRAMSTATE(MainEnvPtrRegion, const MemRegion *)

// Stores the regions of environments returned by getenv calls.
REGISTER_SET_WITH_PROGRAMSTATE(GetenvEnvPtrRegions, const MemRegion *)

// Stores key-value pairs, where key is function declaration and value is
// pointer to memory region returned by previous call of this function
REGISTER_MAP_WITH_PROGRAMSTATE(PreviousCallResultMap, const FunctionDecl *,
const MemRegion *)

const NoteTag *InvalidPtrChecker::createEnvInvalidationNote(
CheckerContext &C, ProgramStateRef State, StringRef FunctionName) const {

const MemRegion *MainRegion = State->get<MainEnvPtrRegion>();
const auto GetenvRegions = State->get<GetenvEnvPtrRegions>();

return C.getNoteTag([this, MainRegion, GetenvRegions,
FunctionName = std::string{FunctionName}](
PathSensitiveBugReport &BR, llvm::raw_ostream &Out) {
// Only handle the BugType of this checker.
if (&BR.getBugType() != &InvalidPtrBugType)
return;

// Mark all regions that were interesting before as NOT interesting now
// to avoid extra notes coming from invalidation points higher up the
// bugpath. This ensures that only the last invalidation point is marked
// with a note tag.
llvm::SmallVector<std::string, 2> InvalidLocationNames;
if (BR.isInteresting(MainRegion)) {
BR.markNotInteresting(MainRegion);
InvalidLocationNames.push_back("the environment parameter of 'main'");
}
bool InterestingGetenvFound = false;
for (const MemRegion *MR : GetenvRegions) {
if (BR.isInteresting(MR)) {
BR.markNotInteresting(MR);
if (!InterestingGetenvFound) {
InterestingGetenvFound = true;
InvalidLocationNames.push_back(
"the environment returned by 'getenv'");
}
}
}

// Emit note tag message.
if (InvalidLocationNames.size() >= 1)
Out << '\'' << FunctionName << "' call may invalidate "
<< InvalidLocationNames[0];
if (InvalidLocationNames.size() == 2)
Out << ", and " << InvalidLocationNames[1];
});
}

void InvalidPtrChecker::EnvpInvalidatingCall(const CallEvent &Call,
CheckerContext &C) const {
StringRef FunctionName = Call.getCalleeIdentifier()->getName();
// This callevent invalidates all previously generated pointers to the
// environment.
ProgramStateRef State = C.getState();
const MemRegion *SymbolicEnvPtrRegion = State->get<EnvPtrRegion>();
if (!SymbolicEnvPtrRegion)
return;

State = State->add<InvalidMemoryRegions>(SymbolicEnvPtrRegion);
if (const MemRegion *MainEnvPtr = State->get<MainEnvPtrRegion>())
State = State->add<InvalidMemoryRegions>(MainEnvPtr);
for (const MemRegion *EnvPtr : State->get<GetenvEnvPtrRegions>())
State = State->add<InvalidMemoryRegions>(EnvPtr);

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'";
});
StringRef FunctionName = Call.getCalleeIdentifier()->getName();
const NoteTag *InvalidationNote =
createEnvInvalidationNote(C, State, FunctionName);

C.addTransition(State, Note);
C.addTransition(State, InvalidationNote);
}

void InvalidPtrChecker::postPreviousReturnInvalidatingCall(
@@ -124,9 +185,9 @@ void InvalidPtrChecker::postPreviousReturnInvalidatingCall(
if (const MemRegion *const *Reg = State->get<PreviousCallResultMap>(FD)) {
const MemRegion *PrevReg = *Reg;
State = State->add<InvalidMemoryRegions>(PrevReg);
Note = C.getNoteTag([PrevReg, FD](PathSensitiveBugReport &BR,
llvm::raw_ostream &Out) {
if (!BR.isInteresting(PrevReg))
Note = C.getNoteTag([this, PrevReg, FD](PathSensitiveBugReport &BR,
llvm::raw_ostream &Out) {
if (!BR.isInteresting(PrevReg) || &BR.getBugType() != &InvalidPtrBugType)
return;
Out << '\'';
FD->getNameForDiagnostic(Out, FD->getASTContext().getLangOpts(), true);
@@ -146,16 +207,15 @@ void InvalidPtrChecker::postPreviousReturnInvalidatingCall(

// Remember to this region.
const auto *SymRegOfRetVal = cast<SymbolicRegion>(RetVal.getAsRegion());
const MemRegion *MR =
const_cast<MemRegion *>(SymRegOfRetVal->getBaseRegion());
const MemRegion *MR = SymRegOfRetVal->getBaseRegion();
State = State->set<PreviousCallResultMap>(FD, MR);

ExplodedNode *Node = C.addTransition(State, Note);
const NoteTag *PreviousCallNote =
C.getNoteTag([MR](PathSensitiveBugReport &BR, llvm::raw_ostream &Out) {
if (!BR.isInteresting(MR))
const NoteTag *PreviousCallNote = C.getNoteTag(
[this, MR](PathSensitiveBugReport &BR, llvm::raw_ostream &Out) {
if (!BR.isInteresting(MR) || &BR.getBugType() != &InvalidPtrBugType)
return;
Out << '\'' << "'previous function call was here" << '\'';
Out << "previous function call was here";
});

C.addTransition(State, Node, PreviousCallNote);
@@ -185,6 +245,18 @@ static const MemRegion *findInvalidatedSymbolicBase(ProgramStateRef State,
// function call as an argument.
void InvalidPtrChecker::checkPostCall(const CallEvent &Call,
CheckerContext &C) const {

ProgramStateRef State = C.getState();

// Model 'getenv' calls
if (GetEnvCall.matches(Call)) {
const MemRegion *Region = Call.getReturnValue().getAsRegion();
if (Region) {
State = State->add<GetenvEnvPtrRegions>(Region);
C.addTransition(State);
}
}

// Check if function invalidates 'envp' argument of 'main'
if (const auto *Handler = EnvpInvalidatingFunctions.lookup(Call))
(this->**Handler)(Call, C);
@@ -193,14 +265,16 @@ void InvalidPtrChecker::checkPostCall(const CallEvent &Call,
if (const auto *Handler = PreviousCallInvalidatingFunctions.lookup(Call))
(this->**Handler)(Call, C);

// If pedantic mode is on, regard 'getenv' calls invalidating as well
if (InvalidatingGetEnv && GetEnvCall.matches(Call))
postPreviousReturnInvalidatingCall(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<SymbolicRegion>(
@@ -218,8 +292,8 @@ void InvalidPtrChecker::checkPostCall(const CallEvent &Call,
C.getASTContext().getPrintingPolicy());
Out << "' in a function call";

auto Report =
std::make_unique<PathSensitiveBugReport>(BT, Out.str(), ErrorNode);
auto Report = std::make_unique<PathSensitiveBugReport>(
InvalidPtrBugType, Out.str(), ErrorNode);
Report->markInteresting(InvalidatedSymbolicBase);
Report->addRange(Call.getArgSourceRange(I));
C.emitReport(std::move(Report));
@@ -243,7 +317,7 @@ void InvalidPtrChecker::checkBeginFunction(CheckerContext &C) const {

// Save the memory region pointed by the environment pointer parameter of
// 'main'.
C.addTransition(State->set<EnvPtrRegion>(EnvpReg));
C.addTransition(State->set<MainEnvPtrRegion>(EnvpReg));
}

// Check if invalidated region is being dereferenced.
@@ -262,13 +336,16 @@ void InvalidPtrChecker::checkLocation(SVal Loc, bool isLoad, const Stmt *S,
return;

auto Report = std::make_unique<PathSensitiveBugReport>(
BT, "dereferencing an invalid pointer", ErrorNode);
InvalidPtrBugType, "dereferencing an invalid pointer", ErrorNode);
Report->markInteresting(InvalidatedSymbolicBase);
C.emitReport(std::move(Report));
}

void ento::registerInvalidPtrChecker(CheckerManager &Mgr) {
Mgr.registerChecker<InvalidPtrChecker>();
auto *Checker = Mgr.registerChecker<InvalidPtrChecker>();
Checker->InvalidatingGetEnv =
Mgr.getAnalyzerOptions().getCheckerBooleanOption(Checker,
"InvalidatingGetEnv");
}

bool ento::shouldRegisterInvalidPtrChecker(const CheckerManager &) {
1 change: 1 addition & 0 deletions clang/test/Analysis/analyzer-config.c
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@
// CHECK-NEXT: alpha.osx.cocoa.DirectIvarAssignment:AnnotatedFunctions = false
// CHECK-NEXT: alpha.security.MmapWriteExec:MmapProtExec = 0x04
// CHECK-NEXT: alpha.security.MmapWriteExec:MmapProtRead = 0x01
// CHECK-NEXT: alpha.security.cert.env.InvalidPtr:InvalidatingGetEnv = false
// CHECK-NEXT: alpha.security.taint.TaintPropagation:Config = ""
// CHECK-NEXT: alpha.unix.Errno:AllowErrnoReadOutsideConditionExpressions = true
// CHECK-NEXT: apply-fixits = false
39 changes: 36 additions & 3 deletions clang/test/Analysis/cert/env34-c-cert-examples.c
Original file line number Diff line number Diff line change
@@ -1,15 +1,48 @@
// Default options.
// RUN: %clang_analyze_cc1 \
// RUN: -analyzer-checker=core,alpha.security.cert.env.InvalidPtr \
// RUN: -verify -Wno-unused %s
//
// Test the laxer handling of getenv function (this is the default).
// RUN: %clang_analyze_cc1 \
// RUN: -analyzer-checker=core,alpha.security.cert.env.InvalidPtr \
// RUN: -analyzer-config alpha.security.cert.env.InvalidPtr:InvalidatingGetEnv=false \
// RUN: -verify -Wno-unused %s
//
// Test the stricter handling of getenv function.
// RUN: %clang_analyze_cc1 \
// RUN: -analyzer-checker=core,alpha.security.cert.env.InvalidPtr \
// RUN: -analyzer-config alpha.security.cert.env.InvalidPtr:InvalidatingGetEnv=true \
// RUN: -verify=expected,pedantic -Wno-unused %s

#include "../Inputs/system-header-simulator.h"
char *getenv(const char *name);
int setenv(const char *name, const char *value, int overwrite);
int strcmp(const char*, const char*);
char *strdup(const char*);
void free(void *memblock);
void *malloc(size_t size);

void incorrect_usage(void) {
void incorrect_usage_setenv_getenv_invalidation(void) {
char *tmpvar;
char *tempvar;

tmpvar = getenv("TMP");

if (!tmpvar)
return;

setenv("TEMP", "", 1); //setenv can invalidate env

if (!tmpvar)
return;

if (strcmp(tmpvar, "") == 0) { // body of strcmp is unknown
// expected-warning@-1{{use of invalidated pointer 'tmpvar' in a function call}}
}
}

void incorrect_usage_double_getenv_invalidation(void) {
char *tmpvar;
char *tempvar;

@@ -18,13 +51,13 @@ void incorrect_usage(void) {
if (!tmpvar)
return;

tempvar = getenv("TEMP");
tempvar = getenv("TEMP"); //getenv should not invalidate env in non-pedantic mode

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}}
// pedantic-warning@-1{{use of invalidated pointer 'tmpvar' in a function call}}
}
}

1 change: 1 addition & 0 deletions clang/test/Analysis/cert/env34-c.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// RUN: %clang_analyze_cc1 \
// RUN: -analyzer-checker=alpha.security.cert.env.InvalidPtr\
// RUN: -analyzer-config alpha.security.cert.env.InvalidPtr:InvalidatingGetEnv=true \
// RUN: -analyzer-output=text -verify -Wno-unused %s

#include "../Inputs/system-header-simulator.h"
63 changes: 63 additions & 0 deletions clang/test/Analysis/invalid-ptr-checker.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// RUN: %clang_analyze_cc1 \
// RUN: -analyzer-checker=alpha.security.cert.env.InvalidPtr \
// RUN: -analyzer-config alpha.security.cert.env.InvalidPtr:InvalidatingGetEnv=false \
// RUN: -analyzer-output=text -verify -Wno-unused %s
//
// RUN: %clang_analyze_cc1 \
// RUN: -analyzer-checker=alpha.security.cert.env.InvalidPtr \
// RUN: -analyzer-config \
// RUN: alpha.security.cert.env.InvalidPtr:InvalidatingGetEnv=true \
// RUN: -analyzer-output=text -verify=expected,pedantic -Wno-unused %s

#include "Inputs/system-header-simulator.h"

char *getenv(const char *name);
int setenv(const char *name, const char *value, int overwrite);
int strcmp(const char *, const char *);

int custom_env_handler(const char **envp);

void getenv_after_getenv(void) {
char *v1 = getenv("V1");
// pedantic-note@-1{{previous function call was here}}

char *v2 = getenv("V2");
// pedantic-note@-1{{'getenv' call may invalidate the result of the previous 'getenv'}}

strcmp(v1, v2);
// pedantic-warning@-1{{use of invalidated pointer 'v1' in a function call}}
// pedantic-note@-2{{use of invalidated pointer 'v1' in a function call}}
}

void setenv_after_getenv(void) {
char *v1 = getenv("VAR1");

setenv("VAR2", "...", 1);
// expected-note@-1{{'setenv' call may invalidate the environment returned by 'getenv'}}

strcmp(v1, "");
// expected-warning@-1{{use of invalidated pointer 'v1' in a function call}}
// expected-note@-2{{use of invalidated pointer 'v1' in a function call}}
}

int main(int argc, const char *argv[], const char *envp[]) {
setenv("VAR", "...", 0);
// expected-note@-1 2 {{'setenv' call may invalidate the environment parameter of 'main'}}

*envp;
// expected-warning@-1 2 {{dereferencing an invalid pointer}}
// expected-note@-2 2 {{dereferencing an invalid pointer}}
}

void multiple_invalidation_no_duplicate_notes(void) {
char *v1 = getenv("VAR1");

setenv("VAR2", "...", 1); // no note here

setenv("VAR3", "...", 1);
// expected-note@-1{{'setenv' call may invalidate the environment returned by 'getenv'}}

strcmp(v1, "");
// expected-warning@-1{{use of invalidated pointer 'v1' in a function call}}
// expected-note@-2{{use of invalidated pointer 'v1' in a function call}}
}