Skip to content

[analyzer][taint] Recognize tainted LazyCompoundVals (4/4) #115919

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 2 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
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
8 changes: 8 additions & 0 deletions clang/lib/StaticAnalyzer/Checkers/Taint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,14 @@ std::vector<SymbolRef> taint::getTaintedSymbolsImpl(ProgramStateRef State,
return getTaintedSymbolsImpl(State, Sym, Kind, returnFirstOnly);
if (const MemRegion *Reg = V.getAsRegion())
return getTaintedSymbolsImpl(State, Reg, Kind, returnFirstOnly);

if (auto LCV = V.getAs<nonloc::LazyCompoundVal>()) {
StoreManager &StoreMgr = State->getStateManager().getStoreManager();
if (auto DefaultVal = StoreMgr.getDefaultBinding(*LCV)) {
return getTaintedSymbolsImpl(State, *DefaultVal, Kind, returnFirstOnly);
}
}

return {};
}

Expand Down
26 changes: 9 additions & 17 deletions clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,23 +68,15 @@ void ExprEngine::performTrivialCopy(NodeBuilder &Bldr, ExplodedNode *Pred,
Bldr.takeNodes(Pred);

assert(ThisRD);
if (!ThisRD->isEmpty()) {
// Load the source value only for non-empty classes.
// Otherwise it'd retrieve an UnknownVal
// and bind it and RegionStore would think that the actual value
// in this region at this offset is unknown.
SVal V = Call.getArgSVal(0);

// If the value being copied is not unknown, load from its location to get
// an aggregate rvalue.
if (std::optional<Loc> L = V.getAs<Loc>())
V = Pred->getState()->getSVal(*L);
else
assert(V.isUnknownOrUndef());
evalBind(Dst, CallExpr, Pred, ThisVal, V, true);
} else {
Dst.Add(Pred);
}
SVal V = Call.getArgSVal(0);

// If the value being copied is not unknown, load from its location to get
// an aggregate rvalue.
if (std::optional<Loc> L = V.getAs<Loc>())
V = Pred->getState()->getSVal(*L);
else
assert(V.isUnknownOrUndef());
evalBind(Dst, CallExpr, Pred, ThisVal, V, true);

PostStmt PS(CallExpr, LCtx);
for (ExplodedNode *N : Dst) {
Expand Down
12 changes: 7 additions & 5 deletions clang/test/Analysis/ctor-trivial-copy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,7 @@ void _01_empty_structs() {
clang_analyzer_dump_val(Empty2); // expected-warning {{lazyCompoundVal}}
clang_analyzer_dump_val(Empty3); // expected-warning {{lazyCompoundVal}}

// Notice that we don't have entries for the copies, only for the original "Empty" object.
// That binding was added by the opaque "conjure" call, directly constructing to the variable "Empty" by copy-elision.
// The copies are not present because the ExprEngine skips the evalBind of empty structs.
// And even if such binds would reach the Store, the Store copies small structs by copying the individual fields,
// and there are no fields within "Empty", thus we wouldn't have any entries anyways.
// We should have the same Conjured symbol for "Empty", "Empty2" and "Empty3".
clang_analyzer_printState();
// CHECK: "store": { "pointer": "0x{{[0-9a-f]+}}", "items": [
// CHECK-NEXT: { "cluster": "GlobalInternalSpaceRegion", "pointer": "0x{{[0-9a-f]+}}", "items": [
Expand All @@ -66,6 +62,12 @@ void _01_empty_structs() {
// CHECK-NEXT: ]},
// CHECK-NEXT: { "cluster": "Empty", "pointer": "0x{{[0-9a-f]+}}", "items": [
// CHECK-NEXT: { "kind": "Default", "offset": 0, "value": "[[EMPTY_CONJ:conj_\$[0-9]+{int, LC[0-9]+, S[0-9]+, #[0-9]+}]]" }
// CHECK-NEXT: ]},
// CHECK-NEXT: { "cluster": "Empty2", "pointer": "0x{{[0-9a-f]+}}", "items": [
// CHECK-NEXT: { "kind": "Default", "offset": 0, "value": "[[EMPTY_CONJ]]" }
// CHECK-NEXT: ]},
// CHECK-NEXT: { "cluster": "Empty3", "pointer": "0x{{[0-9a-f]+}}", "items": [
// CHECK-NEXT: { "kind": "Default", "offset": 0, "value": "[[EMPTY_CONJ]]" }
// CHECK-NEXT: ]}
// CHECK-NEXT: ]},

Expand Down
29 changes: 27 additions & 2 deletions clang/test/Analysis/taint-generic.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
// RUN: %clang_analyze_cc1 -analyzer-checker=optin.taint,core,alpha.security.ArrayBoundV2 -analyzer-config optin.taint.TaintPropagation:Config=%S/Inputs/taint-generic-config.yaml -Wno-format-security -verify -std=c++11 %s
// RUN: %clang_analyze_cc1 -std=c++11 -Wno-format-security \
// RUN: -analyzer-checker=core,optin.taint,alpha.security.ArrayBoundV2,debug.ExprInspection \
// RUN: -analyzer-config optin.taint.TaintPropagation:Config=%S/Inputs/taint-generic-config.yaml \
// RUN: -verify %s

template <typename T> void clang_analyzer_isTainted(T);

#define BUFSIZE 10
int Buffer[BUFSIZE];

int scanf(const char*, ...);
int mySource1();
template <typename T = int> T mySource1();
int mySource3();

typedef struct _FILE FILE;
Expand Down Expand Up @@ -136,3 +141,23 @@ void testReadingFromStdin(char **p) {
fscanf(stdin, "%d", &n);
Buffer[n] = 1; // expected-warning {{Potential out of bound access }}
}

namespace gh114270 {
class Empty {};
class Aggr {
public:
int data;
};

void top() {
int Int = mySource1<int>();
clang_analyzer_isTainted(Int); // expected-warning {{YES}}

Empty E = mySource1<Empty>();
clang_analyzer_isTainted(E); // expected-warning {{YES}}

Aggr A = mySource1<Aggr>();
clang_analyzer_isTainted(A); // expected-warning {{YES}}
clang_analyzer_isTainted(A.data); // expected-warning {{YES}}
}
} // namespace gh114270
Loading