mirror of
https://github.com/github/codeql.git
synced 2026-04-30 11:15:13 +02:00
C++: Naively copy the 'surprising lifetimes' query from Coding Standards and add required metadata.
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
void c_api(const char*);
|
||||
|
||||
void bad_call_c_api() {
|
||||
// BAD: the memory returned by `c_str()` is freed when the temporary string is destroyed
|
||||
const char* p = std::string("hello").c_str();
|
||||
c_api(p);
|
||||
}
|
||||
|
||||
void good_call_c_api() {
|
||||
// GOOD: the "hello" string outlives the pointer returned by `c_str()`, so it's safe to pass it to `c_api()`
|
||||
std::string hello("hello");
|
||||
const char* p = hello.c_str();
|
||||
c_api(p);
|
||||
}
|
||||
|
||||
void bad_remove_even_numbers(std::vector<int>& v) {
|
||||
// BAD: the iterator is invalidated after the call to `erase`.
|
||||
for(std::vector<int>::iterator it = v.begin(); it != v.end(); ++it) {
|
||||
if(*it % 2 == 0) {
|
||||
v.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void good_remove_even_numbers(std::vector<int>& v) {
|
||||
// GOOD: `erase` returns the iterator to the next element.
|
||||
for(std::vector<int>::iterator it = v.begin(); it != v.end(); ) {
|
||||
if(*it % 2 == 0) {
|
||||
it = v.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
Using an object after its lifetime has ended results in undefined behavior.
|
||||
When an object's lifetime has ended it relinquishes ownership of its resources and the memory it occupied may be reused for other purposes.
|
||||
If the object is accessed after its lifetime has ended, the program may crash or behave in unexpected ways.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>
|
||||
Ensure that no objct is accessed after its lifetime has ended.
|
||||
Use RAII ("Resource Acquisition Is Initialization") to manage the lifetime of objects, and avoid manual memory management, if possible.
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>
|
||||
The following two functions demonstrate common lifetime violations when working with the C++ standard library.
|
||||
|
||||
The <code>bad_call_c_api</code> function contains a use of an expired lifetime.
|
||||
First, a temporary object of type <code>std::string</code> is constructed, and a pointer to its internal buffer is stored in a local variable.
|
||||
Once the <code>c_str()</code> call returns, the temporary object is destroyed, and the memory pointed to by <code>p</code> is freed.
|
||||
Thus, any attempt to dereference <code>p</code> inside <code>c_api</code> will result in a use-after-free vulnerability.
|
||||
|
||||
The <code>good_call_c_api</code> function contains a fixed version of the first example.
|
||||
The variable <code>hello</code> is declared as a local variable, and the pointer to its internal buffer is stored in <code>p</code>.
|
||||
The lifetime of hello outlives the call to <code>c_api</code>, so the pointer stored in <code>p</code> remains valid throughout the call to <code>c_api</code>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The <code>bad_remove_even_numbers</code> function demonstrates a potential issue with iterator invalidation.
|
||||
Each C++ standard library container comes with a specification of which operations invalidates iterators pointing into the container.
|
||||
For example, calling <code>erase</code> on an object of type <code>std::vector<T></code> invalidates all its iterators, and thus any attempt to dereference the iterator can result in a use-after-free vulnerability.
|
||||
|
||||
The <code>good_remove_even_numbers</code> function contains a fixd version of the third example.
|
||||
The <code>erase</code> function returns an iterator to the element following the last element removed, and this return value is used to ensure that <code>it</code> remains valid after the call to <code>erase</code>.
|
||||
|
||||
</p>
|
||||
<sample src="UseAfterExpiredLifetime.cpp" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>CERT C Coding Standard:
|
||||
<a href="https://wiki.sei.cmu.edu/confluence/display/c/MEM30-C.+Do+not+access+freed+memory">MEM30-C. Do not access freed memory</a>.</li>
|
||||
<li>
|
||||
OWASP:
|
||||
<a href="https://owasp.org/www-community/vulnerabilities/Using_freed_memory">Using freed memory</a>.
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/isocpp/CppCoreGuidelines/blob/master/docs/Lifetime.pdf">Lifetime safety: Preventing common dangling</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://en.cppreference.com/w/cpp/container">Containers library</a>
|
||||
</li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,736 @@
|
||||
/**
|
||||
* @id cpp/use-after-expired-lifetime
|
||||
* @name Use of object after its lifetime has ended
|
||||
* @description Accessing an object after its lifetime has ended can result in security vulnerabilities and undefined behavior.
|
||||
* @kind problem
|
||||
* @precision medium
|
||||
* @problem.severity error
|
||||
* @tags correctness
|
||||
* security
|
||||
* experimental
|
||||
* external/cwe/cwe-416
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import semmle.code.cpp.dataflow.DataFlow
|
||||
import semmle.code.cpp.controlflow.Nullness
|
||||
|
||||
class StarOperator extends Operator {
|
||||
StarOperator() {
|
||||
this.hasName("operator*") and
|
||||
this.getNumberOfParameters() = 0
|
||||
}
|
||||
}
|
||||
|
||||
class IncrementOperator extends Operator {
|
||||
IncrementOperator() {
|
||||
this.hasName("operator++") and
|
||||
this.getNumberOfParameters() = 0
|
||||
}
|
||||
}
|
||||
|
||||
class StructureDerefOperator extends Operator {
|
||||
StructureDerefOperator() {
|
||||
this.hasName("operator->") and
|
||||
this.getNumberOfParameters() = 0
|
||||
}
|
||||
}
|
||||
|
||||
class SubscriptOperator extends Operator {
|
||||
SubscriptOperator() {
|
||||
this.hasName("operator[]") and
|
||||
this.getNumberOfParameters() = 1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A type which is an `Indirection` type according to the Lifetime profile.
|
||||
*
|
||||
* An indirection type is either a `LifetimePointerType` or `LifetimeOwnerType`.
|
||||
*/
|
||||
abstract class LifetimeIndirectionType extends Type {
|
||||
/**
|
||||
* Gets the `DerefType` of this indirection type.
|
||||
*
|
||||
* This corresponds to the owned or pointed to type.
|
||||
*/
|
||||
Type getDerefType() {
|
||||
result = this.(PointerType).getBaseType()
|
||||
or
|
||||
result = this.(ReferenceType).getBaseType()
|
||||
or
|
||||
exists(MemberFunction mf | mf.getDeclaringType() = this |
|
||||
result = mf.(StarOperator).getType().getUnspecifiedType().(ReferenceType).getBaseType()
|
||||
or
|
||||
result = mf.(SubscriptOperator).getType().getUnspecifiedType().(ReferenceType).getBaseType()
|
||||
or
|
||||
result =
|
||||
mf.(StructureDerefOperator).getType().getUnspecifiedType().(PointerType).getBaseType()
|
||||
or
|
||||
mf.getName() = "begin" and
|
||||
result = mf.getType().(LifetimePointerType).getDerefType()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A lifetime owner type.
|
||||
*
|
||||
* A type which owns another object. For example, `std::unique_ptr`. Includes
|
||||
* `LifetimeSharedOwnerType`.
|
||||
*/
|
||||
class LifetimeOwnerType extends LifetimeIndirectionType {
|
||||
LifetimeOwnerType() {
|
||||
// Any shared owner types are also owner types
|
||||
this instanceof LifetimeSharedOwnerType
|
||||
or
|
||||
// This is a container type, or a type with a star operator and..
|
||||
(
|
||||
this instanceof ContainerType
|
||||
or
|
||||
exists(StarOperator mf | mf.getDeclaringType() = this)
|
||||
) and
|
||||
// .. has a "user" provided destructor
|
||||
exists(Destructor d |
|
||||
d.getDeclaringType() = this and
|
||||
not d.isCompilerGenerated()
|
||||
)
|
||||
or
|
||||
// Any specified version of an owner type is also an owner type
|
||||
this.getUnspecifiedType() instanceof LifetimeOwnerType
|
||||
or
|
||||
// Has a field which is a lifetime owner type
|
||||
this.(Class).getAField().getType() instanceof LifetimeOwnerType
|
||||
or
|
||||
// Derived from a public base class which is a owner type
|
||||
exists(ClassDerivation cd |
|
||||
cd = this.(Class).getADerivation() and
|
||||
cd.getBaseClass() instanceof LifetimeOwnerType and
|
||||
cd.getASpecifier().hasName("public")
|
||||
)
|
||||
or
|
||||
// Lifetime profile treats the following types as owner types, even though they don't fully
|
||||
// adhere to the requirements above
|
||||
this.(Class)
|
||||
.hasQualifiedName("std",
|
||||
["stack", "queue", "priority_queue", "optional", "variant", "any", "regex"])
|
||||
or
|
||||
// Explicit annotation on the type
|
||||
this.getAnAttribute().getName().matches("gsl::Owner%")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A `ContainerType`, based on `[container.requirements]` with some adaptions to capture more real
|
||||
* world containers.
|
||||
*/
|
||||
class ContainerType extends Class {
|
||||
ContainerType() {
|
||||
// We use a simpler set of heuristics than the `[container.requirements]`, requiring only
|
||||
// `begin()`/`end()`/`size()` as the minimum API for something to be considered a "container"
|
||||
// type
|
||||
this.getAMemberFunction().getName() = "begin" and
|
||||
this.getAMemberFunction().getName() = "end" and
|
||||
this.getAMemberFunction().getName() = "size"
|
||||
or
|
||||
// This class is a `ContainerType` if it is constructed from a `ContainerType`. This is
|
||||
// important, because templates may not have instantiated all the required member functions
|
||||
exists(TemplateClass tc |
|
||||
this.isConstructedFrom(tc) and
|
||||
tc instanceof ContainerType
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A lifetime "shared owner" type.
|
||||
*
|
||||
* A shared owner is type that "owns" another object, and shares that ownership with other owners.
|
||||
* Examples include `std::shared_ptr` along with other reference counting types.
|
||||
*/
|
||||
class LifetimeSharedOwnerType extends Type {
|
||||
LifetimeSharedOwnerType() {
|
||||
/*
|
||||
* Find all types which can be dereferenced (i.e. have unary * operator), and are therefore
|
||||
* likely to be "owner"s or "pointer"s to other objects. We then consider these classes to be
|
||||
* shared owners if:
|
||||
* - They can be copied (a unique "owner" type would not be copyable)
|
||||
* - They can destroyed
|
||||
*/
|
||||
|
||||
// unary * (i.e. can be dereferenced)
|
||||
exists(StarOperator mf | mf.getDeclaringType() = this) and
|
||||
// "User" provided destructor
|
||||
exists(Destructor d |
|
||||
d.getDeclaringType() = this and
|
||||
not d.isCompilerGenerated()
|
||||
) and
|
||||
// A copy constructor and copy assignment operator
|
||||
exists(CopyConstructor cc | cc.getDeclaringType() = this and not cc.isDeleted()) and
|
||||
exists(CopyAssignmentOperator cc | cc.getDeclaringType() = this and not cc.isDeleted())
|
||||
or
|
||||
// This class is a `SharedOwnerType` if it is constructed from a `SharedOwnerType`. This is
|
||||
// important, because templates may not have instantiated all the required member functions
|
||||
exists(TemplateClass tc |
|
||||
this.(Class).isConstructedFrom(tc) and
|
||||
tc instanceof LifetimeSharedOwnerType
|
||||
)
|
||||
or
|
||||
// Any specified version of a shared owner type is also a shared owner type
|
||||
this.getUnspecifiedType() instanceof LifetimeSharedOwnerType
|
||||
or
|
||||
// Has a field which is a lifetime shared owner type
|
||||
this.(Class).getAField().getType() instanceof LifetimeSharedOwnerType
|
||||
or
|
||||
// Derived from a public base class which is a shared owner type
|
||||
exists(ClassDerivation cd |
|
||||
cd = this.(Class).getADerivation() and
|
||||
cd.getBaseClass() instanceof LifetimeSharedOwnerType and
|
||||
cd.getASpecifier().hasName("public")
|
||||
)
|
||||
or
|
||||
// Lifetime profile treats the following types as shared owner types, even though they don't
|
||||
// fully adhere to the requirements above
|
||||
this.(Class).hasQualifiedName("std", "shared_future")
|
||||
or
|
||||
// Explicit annotation on the type
|
||||
this.getAnAttribute().getName().matches("gsl::SharedOwner%")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An `IteratorType`, based on `[iterator.requirements]` with some adaptions to capture more real
|
||||
* world iterators.
|
||||
*/
|
||||
class IteratorType extends Type {
|
||||
IteratorType() {
|
||||
// We consider anything with an increment and * operator to be sufficient to be an iterator type
|
||||
exists(StarOperator mf |
|
||||
mf.getDeclaringType() = this and mf.getType().getUnspecifiedType() instanceof ReferenceType
|
||||
) and
|
||||
exists(IncrementOperator op |
|
||||
op.getDeclaringType() = this and op.getType().(ReferenceType).getBaseType() = this
|
||||
)
|
||||
or
|
||||
// Along with unspecified versions of the types above
|
||||
this.getUnspecifiedType() instanceof IteratorType
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A lifetime pointer type.
|
||||
*
|
||||
* A type which points to another object. For example, `std::unique_ptr`. Includes
|
||||
* `LifetimeSharedOwnerType`.
|
||||
*/
|
||||
class LifetimePointerType extends LifetimeIndirectionType {
|
||||
LifetimePointerType() {
|
||||
this instanceof IteratorType
|
||||
or
|
||||
this instanceof PointerType
|
||||
or
|
||||
this instanceof ReferenceType
|
||||
or
|
||||
// A shared owner type is a pointer type, but an owner type is not.
|
||||
this instanceof LifetimeSharedOwnerType and
|
||||
not this instanceof LifetimeOwnerType
|
||||
or
|
||||
this.(Class).hasQualifiedName("std", "reference_wrapper")
|
||||
or
|
||||
exists(Class vectorBool, UserType reference |
|
||||
vectorBool.hasQualifiedName("std", "vector") and
|
||||
vectorBool.getATemplateArgument() instanceof BoolType and
|
||||
reference.hasName("reference") and
|
||||
reference.getDeclaringType() = vectorBool and
|
||||
this = reference.getUnderlyingType()
|
||||
)
|
||||
or
|
||||
// Any specified version of a pointer type is also an owner type
|
||||
this.getUnspecifiedType() instanceof LifetimePointerType
|
||||
or
|
||||
// Has a field which is a lifetime pointer type
|
||||
this.(Class).getAField().getType() instanceof LifetimePointerType
|
||||
or
|
||||
// Derived from a public base class which is a pointer type
|
||||
exists(ClassDerivation cd |
|
||||
cd = this.(Class).getADerivation() and
|
||||
cd.getBaseClass() instanceof LifetimePointerType and
|
||||
cd.getASpecifier().hasName("public")
|
||||
)
|
||||
or
|
||||
// Explicit annotation on the type
|
||||
this.getAnAttribute().getName().matches("gsl::Pointer%")
|
||||
}
|
||||
}
|
||||
|
||||
/** A full expression as defined in [intro.execution] of N3797. */
|
||||
class FullExpr extends Expr {
|
||||
FullExpr() {
|
||||
// A full-expression is not a subexpression
|
||||
not exists(Expr p | this.getParent() = p)
|
||||
or
|
||||
// A sub-expression that is an unevaluated operand
|
||||
this.isUnevaluated()
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets the `FullExpression` scope of the `TemporaryObjectExpr`. */
|
||||
FullExpr getTemporaryObjectExprScope(TemporaryObjectExpr toe) {
|
||||
result = toe.getUnconverted().getParent*()
|
||||
}
|
||||
|
||||
/**
|
||||
* See `LifetimeLocalVariable` and subclasses.
|
||||
*/
|
||||
private newtype TLifetimeLocalVariable =
|
||||
TLocalScopeVariable(LocalScopeVariable lsv) { not lsv.isStatic() } or
|
||||
TTemporaryObject(TemporaryObjectExpr toe)
|
||||
|
||||
/*
|
||||
* Note, the lifetime profile also supports locally initialized _aggregates_, which we could
|
||||
* support with something like this:
|
||||
* ```
|
||||
* TAggregateField(TLocalScopeVariable base, Field f) {
|
||||
* exists(LocalScopeVariable lsv |
|
||||
* base = TLocalScopeVariable(lsv) and
|
||||
* lsv.getType() = f.getDeclaringType() and
|
||||
* lsv.getType() instanceof LifetimeAggregateType
|
||||
* )
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
|
||||
/**
|
||||
* A "LocalVariable" as defined by the lifetime profile.
|
||||
*
|
||||
* This includes newly introduced objects with a local scope.
|
||||
*/
|
||||
class LifetimeLocalVariable extends TLifetimeLocalVariable {
|
||||
string toString() { none() } // specified in sub-classes
|
||||
|
||||
Type getType() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A parameter or `LocalVariable`, used as a `LifetimeLocalVariable`
|
||||
*/
|
||||
class LifetimeLocalScopeVariable extends TLocalScopeVariable, LifetimeLocalVariable {
|
||||
LocalScopeVariable getVariable() { this = TLocalScopeVariable(result) }
|
||||
|
||||
override Type getType() { result = this.getVariable().getType() }
|
||||
|
||||
override string toString() { result = this.getVariable().toString() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A temporary object used as a `LifetimeLocalVariable`.
|
||||
*/
|
||||
class LifetimeTemporaryObject extends TTemporaryObject, LifetimeLocalVariable {
|
||||
TemporaryObjectExpr getTemporaryObjectExpr() { this = TTemporaryObject(result) }
|
||||
|
||||
override Type getType() { result = this.getTemporaryObjectExpr().getType() }
|
||||
|
||||
override string toString() { result = this.getTemporaryObjectExpr().toString() }
|
||||
}
|
||||
|
||||
newtype TInvalidReason =
|
||||
/** LifetimeLocalVariable is invalid because it hasn't been initialized. */
|
||||
TUninitialized(DeclStmt ds, Variable v) { ds.getADeclaration() = v } or
|
||||
/** LifetimeLocalVariable is invalid because it points to a variable which has gone out of scope. */
|
||||
TVariableOutOfScope(LocalScopeVariable v, ControlFlowNode cfn) { goesOutOfScopeAt(v, cfn) } or
|
||||
/** LifetimeLocalVariable is invalid because it points to a temporary object expression which has gone out of scope. */
|
||||
TTemporaryOutOfScope(TemporaryObjectExpr toe) or
|
||||
/** LifetimeLocalVariable is invalid because it points to data held by an owner which has since been invalidated. */
|
||||
TOwnerModified(FunctionCall fc)
|
||||
|
||||
/**
|
||||
* A reason why a pointer may be invalid.
|
||||
*/
|
||||
class InvalidReason extends TInvalidReason {
|
||||
/** Holds if this reason indicates the pointer is accessed before the lifetime of an object began. */
|
||||
predicate isBeforeLifetime() { this instanceof TUninitialized }
|
||||
|
||||
/** Holds if this reason indicates the pointer is accessed after the lifetime of an object has finished. */
|
||||
predicate isAfterLifetime() { not this.isBeforeLifetime() }
|
||||
|
||||
/** Gets a description of the reason why this pointer may be invalid. */
|
||||
string getDescription() {
|
||||
exists(DeclStmt ds, Variable v |
|
||||
this = TUninitialized(ds, v) and
|
||||
result = "variable " + v.getName() + " was never initialized"
|
||||
)
|
||||
or
|
||||
exists(LocalScopeVariable v, ControlFlowNode cfn |
|
||||
this = TVariableOutOfScope(v, cfn) and
|
||||
result = "variable " + v.getName() + " went out of scope"
|
||||
)
|
||||
or
|
||||
exists(TemporaryObjectExpr toe |
|
||||
this = TTemporaryOutOfScope(toe) and
|
||||
result = "temporary object went out of scope"
|
||||
)
|
||||
or
|
||||
exists(FunctionCall fc |
|
||||
this = TOwnerModified(fc) and
|
||||
result = "owner type was modified"
|
||||
)
|
||||
}
|
||||
|
||||
string toString() { result = this.getDescription() }
|
||||
|
||||
/** Get an element that explains the reason for the invalid determination. */
|
||||
private Element getExplanatoryElement() {
|
||||
exists(DeclStmt ds |
|
||||
this = TUninitialized(ds, _) and
|
||||
result = ds
|
||||
)
|
||||
or
|
||||
exists(ControlFlowNode cfn |
|
||||
this = TVariableOutOfScope(_, cfn) and
|
||||
result = cfn
|
||||
)
|
||||
or
|
||||
exists(TemporaryObjectExpr toe |
|
||||
this = TTemporaryOutOfScope(toe) and
|
||||
result = getTemporaryObjectExprScope(toe)
|
||||
)
|
||||
or
|
||||
exists(FunctionCall fc |
|
||||
this = TOwnerModified(fc) and
|
||||
result = fc
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a `message` for use in alert messages.
|
||||
*
|
||||
* The message will contain a `$@` placeholder, for which `explanation` and `explanationDesc` are
|
||||
* the placeholder components which should be added as extra columns.
|
||||
*/
|
||||
predicate hasMessage(string message, Element explanation, string explanationDesc) {
|
||||
message = "because the " + this.getDescription() + " $@." and
|
||||
explanation = this.getExplanatoryElement() and
|
||||
explanationDesc = "here"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A reason why a pointer may be null.
|
||||
*/
|
||||
newtype TNullReason =
|
||||
// Null because the `NullValue` was assigned
|
||||
TNullAssignment(NullValue e)
|
||||
|
||||
class NullReason extends TNullReason {
|
||||
/** Gets a description of the reason why this pointer may be null. */
|
||||
string getDescription() {
|
||||
exists(NullValue nv |
|
||||
this = TNullAssignment(nv) and
|
||||
result = "null value was assigned"
|
||||
)
|
||||
}
|
||||
|
||||
string toString() { result = this.getDescription() }
|
||||
}
|
||||
|
||||
/** See `PSetEntry` and subclasses. */
|
||||
newtype TPSetEntry =
|
||||
/** Points to a lifetime local variable. */
|
||||
PSetVar(LifetimeLocalVariable lv) or
|
||||
/** Points to a lifetime local variable that represents an owner type. */
|
||||
PSetOwner(LifetimeLocalVariable lv, int level) {
|
||||
level = [0 .. 2] and lv.getType() instanceof LifetimeOwnerType
|
||||
} or
|
||||
/** Points to a global variable. */
|
||||
PSetGlobal() or
|
||||
/** A null pointer. */
|
||||
PSetNull(NullReason nr) or
|
||||
/** An invalid pointer, for the given reason. */
|
||||
PSetInvalid(InvalidReason ir) or
|
||||
/** An unknown pointer. */
|
||||
PSetUnknown()
|
||||
|
||||
/**
|
||||
* An entry in the points-to set for a particular "LifetimeLocalVariable" at a particular
|
||||
* point in the program.
|
||||
*/
|
||||
class PSetEntry extends TPSetEntry {
|
||||
string toString() {
|
||||
exists(LifetimeLocalVariable lv |
|
||||
this = PSetVar(lv) and
|
||||
result = "Var(" + lv.toString() + ")"
|
||||
)
|
||||
or
|
||||
this = PSetGlobal() and result = "global"
|
||||
or
|
||||
exists(LifetimeLocalVariable lv, int level |
|
||||
this = PSetOwner(lv, level) and
|
||||
result = "Owner(" + lv.toString() + "," + level + ")"
|
||||
)
|
||||
or
|
||||
exists(NullReason nr | this = PSetNull(nr) and result = "null because" + nr)
|
||||
or
|
||||
exists(InvalidReason ir | this = PSetInvalid(ir) and result = "invalid because " + ir)
|
||||
or
|
||||
this = PSetUnknown() and result = "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The "pmap" or "points-to map" for a "lifetime" local variable.
|
||||
*/
|
||||
predicate pointsToMap(ControlFlowNode cfn, LifetimeLocalVariable lv, PSetEntry ps) {
|
||||
if isPSetReassigned(cfn, lv)
|
||||
then ps = getAnAssignedPSetEntry(cfn, lv)
|
||||
else
|
||||
// Exclude unknown for now
|
||||
exists(ControlFlowNode pred, PSetEntry prevPSet |
|
||||
pred = cfn.getAPredecessor() and
|
||||
pointsToMap(pred, lv, prevPSet) and
|
||||
// Not PSetNull() and a non-null successor of a null check
|
||||
not exists(AnalysedExpr ae |
|
||||
ps = PSetNull(_) and
|
||||
cfn = ae.getNonNullSuccessor(lv.(LifetimeLocalScopeVariable).getVariable())
|
||||
) and
|
||||
// lv is not out of scope at this node
|
||||
not goesOutOfScopeAt(lv.(LifetimeLocalScopeVariable).getVariable(), cfn)
|
||||
|
|
||||
// Propagate a PSetEntry from the predecessor node, so long as the
|
||||
// PSetEntry is not invalidated at this node
|
||||
ps = prevPSet and
|
||||
not exists(getAnInvalidation(prevPSet, cfn))
|
||||
or
|
||||
// Replace prevPSet with an invalidation reason at this node
|
||||
ps = getAnInvalidation(prevPSet, cfn)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate isPSetReassigned(ControlFlowNode cfn, LifetimeLocalVariable lv) {
|
||||
exists(DeclStmt ds |
|
||||
cfn = ds and
|
||||
ds.getADeclaration() = lv.(LifetimeLocalScopeVariable).getVariable() and
|
||||
lv.getType() instanceof PointerType
|
||||
)
|
||||
or
|
||||
exists(TemporaryObjectExpr toe |
|
||||
toe = lv.(LifetimeTemporaryObject).getTemporaryObjectExpr() and
|
||||
cfn = toe
|
||||
)
|
||||
or
|
||||
// Assigned a value
|
||||
cfn = lv.(LifetimeLocalScopeVariable).getVariable().getAnAssignedValue()
|
||||
or
|
||||
// If the address of a local var is passed to a function, then assume it initializes it
|
||||
exists(Call fc, AddressOfExpr aoe |
|
||||
cfn = aoe and
|
||||
fc.getAnArgument() = aoe and
|
||||
lv.(LifetimeLocalScopeVariable).getVariable() = aoe.getOperand().(VariableAccess).getTarget()
|
||||
)
|
||||
}
|
||||
|
||||
/** Is the `lv` assigned or reassigned at this ControlFlowNode `cfn`. */
|
||||
private PSetEntry getAnAssignedPSetEntry(ControlFlowNode cfn, LifetimeLocalVariable lv) {
|
||||
exists(DeclStmt ds |
|
||||
cfn = ds and
|
||||
ds.getADeclaration() = lv.(LifetimeLocalScopeVariable).getVariable()
|
||||
|
|
||||
lv.getType() instanceof PointerType and
|
||||
result = PSetInvalid(TUninitialized(ds, lv.(LifetimeLocalScopeVariable).getVariable()))
|
||||
)
|
||||
or
|
||||
exists(TemporaryObjectExpr toe |
|
||||
toe = lv.(LifetimeTemporaryObject).getTemporaryObjectExpr() and
|
||||
cfn = toe and
|
||||
result = PSetVar(lv)
|
||||
)
|
||||
or
|
||||
// Assigned a value
|
||||
exists(Expr assign |
|
||||
assign = lv.(LifetimeLocalScopeVariable).getVariable().getAnAssignedValue() and
|
||||
cfn = assign
|
||||
|
|
||||
if isKnownAssignmentType(assign)
|
||||
then knownAssignmentType(assign, result)
|
||||
else result = PSetUnknown()
|
||||
)
|
||||
or
|
||||
// If the address of a local var is passed to a function, then assume it initializes it
|
||||
exists(Call fc, AddressOfExpr aoe |
|
||||
cfn = aoe and
|
||||
fc.getAnArgument() = aoe and
|
||||
lv.(LifetimeLocalScopeVariable).getVariable() = aoe.getOperand().(VariableAccess).getTarget() and
|
||||
result = PSetUnknown()
|
||||
)
|
||||
}
|
||||
|
||||
predicate isKnownAssignmentType(Expr assign) {
|
||||
assign = any(LocalScopeVariable lv).getAnAssignedValue() and
|
||||
(
|
||||
exists(Variable v | v = assign.(AddressOfExpr).getOperand().(VariableAccess).getTarget() |
|
||||
v instanceof LocalScopeVariable
|
||||
or
|
||||
v instanceof GlobalVariable
|
||||
)
|
||||
or
|
||||
// Assignment of a previous variable
|
||||
exists(VariableAccess va |
|
||||
va = assign and
|
||||
va.getTarget().(LocalScopeVariable).getType() instanceof LifetimePointerType
|
||||
)
|
||||
or
|
||||
assign instanceof NullValue
|
||||
or
|
||||
exists(FunctionCall fc |
|
||||
assign = fc and
|
||||
fc.getNumberOfArguments() = 0 and
|
||||
fc.getType() instanceof LifetimePointerType
|
||||
|
|
||||
// A function call is a product of its inputs (just handle qualifiers at the moment)
|
||||
exists(LifetimeLocalVariable lv |
|
||||
lv = TTemporaryObject(fc.getQualifier().getConversion())
|
||||
or
|
||||
lv = TLocalScopeVariable(fc.getQualifier().(VariableAccess).getTarget())
|
||||
|
|
||||
lv.getType() instanceof LifetimePointerType
|
||||
or
|
||||
lv.getType() instanceof LifetimeOwnerType
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* An expression which is assigned to a `LocalScopeVariable`, which has a known PSet value i.e. not
|
||||
* an "Unknown" PSet value.
|
||||
*/
|
||||
predicate knownAssignmentType(Expr assign, PSetEntry ps) {
|
||||
assign = any(LocalScopeVariable lv).getAnAssignedValue() and
|
||||
(
|
||||
// The assigned value is `&v`
|
||||
exists(Variable v | v = assign.(AddressOfExpr).getOperand().(VariableAccess).getTarget() |
|
||||
v instanceof LocalScopeVariable and
|
||||
(
|
||||
// If the variable we are taking the address of is a reference type, then we are really
|
||||
// taking the address of whatever the reference type "points-to". Use the `pointsToMap`
|
||||
// to determine viable `LifetimeLocalScopeVariable`s this could point to.
|
||||
if v.getType() instanceof ReferenceType
|
||||
then
|
||||
pointsToMap(assign.getAPredecessor(),
|
||||
any(LifetimeLocalScopeVariable lv | lv.getVariable() = v), ps)
|
||||
else
|
||||
// This assignment points-to `v` itself.
|
||||
ps = PSetVar(TLocalScopeVariable(v))
|
||||
)
|
||||
or
|
||||
// If the variable we are taking the address of is a reference variable, then this points-to
|
||||
// a global. If the variable we taking the address of is a reference type, we need to consider
|
||||
// that it might point-to a global, even if it is a LocalScopeVariable (this case is required
|
||||
// so that we still produce a result even if the pointsToMap is empty for `lv`).
|
||||
(v instanceof GlobalVariable or v.getType() instanceof ReferenceType) and
|
||||
ps = PSetGlobal()
|
||||
)
|
||||
or
|
||||
// Assignment of a previous variable
|
||||
exists(VariableAccess va |
|
||||
va = assign and
|
||||
va.getTarget().(LocalScopeVariable).getType() instanceof LifetimePointerType and
|
||||
// PSet of that become PSet of this
|
||||
pointsToMap(assign.getAPredecessor(),
|
||||
any(LifetimeLocalScopeVariable lv | lv.getVariable() = va.getTarget()), ps)
|
||||
)
|
||||
or
|
||||
// The `NullValue` class covers all types of null equivalent expressions. This case also handles
|
||||
// default and value initialization, where an "implicit" null value expression is added by the
|
||||
// extractor
|
||||
assign instanceof NullValue and ps = PSetNull(TNullAssignment(assign))
|
||||
or
|
||||
exists(FunctionCall fc |
|
||||
assign = fc and
|
||||
// If the assignment is being converted via a ReferenceDereferenceExpr, then
|
||||
// we are essentially copying the original object
|
||||
not assign.getFullyConverted() instanceof ReferenceDereferenceExpr and
|
||||
fc.getNumberOfArguments() = 0 and
|
||||
fc.getType() instanceof LifetimePointerType
|
||||
|
|
||||
// A function call is a product of its inputs (just handle qualifiers at the moment)
|
||||
exists(LifetimeLocalVariable lv |
|
||||
lv = TTemporaryObject(fc.getQualifier().getConversion())
|
||||
or
|
||||
lv = TLocalScopeVariable(fc.getQualifier().(VariableAccess).getTarget())
|
||||
|
|
||||
ps = PSetVar(lv) and lv.getType() instanceof LifetimePointerType
|
||||
or
|
||||
ps = PSetOwner(lv, 0) and lv.getType() instanceof LifetimeOwnerType
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `cfn` is a node that occur directly after the local scope variable `lv` has gone out of scope.
|
||||
*/
|
||||
predicate goesOutOfScopeAt(LocalScopeVariable lv, ControlFlowNode cfn) {
|
||||
exists(BlockStmt scope |
|
||||
scope = lv.getParentScope() and
|
||||
scope.getAChild+() = cfn.getAPredecessor().getEnclosingStmt() and
|
||||
not scope.getAChild+() = cfn.getEnclosingStmt()
|
||||
)
|
||||
}
|
||||
|
||||
PSetInvalid getAnInvalidation(PSetEntry ps, ControlFlowNode cfn) {
|
||||
exists(LifetimeLocalScopeVariable lv | ps = PSetVar(lv) |
|
||||
result = PSetInvalid(TVariableOutOfScope(lv.getVariable(), cfn))
|
||||
)
|
||||
or
|
||||
exists(LifetimeLocalScopeVariable lv | ps = PSetOwner(lv, _) |
|
||||
result = PSetInvalid(TVariableOutOfScope(lv.getVariable(), cfn))
|
||||
or
|
||||
exists(FunctionCall fc |
|
||||
fc = cfn and
|
||||
fc.getQualifier() = lv.getVariable().getAnAccess() and
|
||||
not fc.getTarget() instanceof ConstMemberFunction and
|
||||
// non-const versions of begin and end should nevertheless be considered const
|
||||
not fc.getTarget().hasName(["begin", "end"]) and
|
||||
result = PSetInvalid(TOwnerModified(fc))
|
||||
)
|
||||
)
|
||||
or
|
||||
// temporary objects end after the full expression
|
||||
exists(LifetimeTemporaryObject lto |
|
||||
ps = PSetVar(lto)
|
||||
or
|
||||
ps = PSetOwner(lto, _)
|
||||
|
|
||||
cfn = lto.getTemporaryObjectExpr().getUnconverted().getParent*().(FullExpr).getASuccessor() and
|
||||
result = PSetInvalid(TTemporaryOutOfScope(lto.getTemporaryObjectExpr()))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* An expression which is dereferenced and may be an "invalid" value.
|
||||
*/
|
||||
class InvalidDereference extends VariableAccess {
|
||||
InvalidReason ir;
|
||||
|
||||
InvalidDereference() {
|
||||
// The local points to map suggests this points to an invalid set
|
||||
exists(LocalScopeVariable lv |
|
||||
lv = this.getTarget() and
|
||||
pointsToMap(this, TLocalScopeVariable(lv), PSetInvalid(ir))
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a reason why this dereference could point to an invalid value. */
|
||||
InvalidReason getAnInvalidReason() { result = ir }
|
||||
}
|
||||
|
||||
from
|
||||
InvalidDereference e, Element explanation, string explanationDesc, InvalidReason ir,
|
||||
string invalidMessage
|
||||
where
|
||||
ir = e.getAnInvalidReason() and
|
||||
ir.isAfterLifetime() and
|
||||
ir.hasMessage(invalidMessage, explanation, explanationDesc)
|
||||
select e,
|
||||
e.(VariableAccess).getTarget().getName() + " is dereferenced here but accesses invalid memory " +
|
||||
invalidMessage, explanation, explanationDesc
|
||||
Reference in New Issue
Block a user