Skip to content

Commit

Permalink
Merge branch 'main' into redsun82/go
Browse files Browse the repository at this point in the history
  • Loading branch information
redsun82 committed May 2, 2024
2 parents 94212d1 + 7d92ec5 commit a8d3226
Show file tree
Hide file tree
Showing 188 changed files with 1,457 additions and 273 deletions.
17 changes: 17 additions & 0 deletions cpp/ql/lib/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
## 0.13.0

### Breaking Changes

* Deleted the deprecated `GlobalValueNumberingImpl.qll` implementation.

### New Features

* Models-as-Data support has been added for C/C++. This feature allows flow sources, sinks and summaries to be expressed in compact strings as an alternative to modelling each source / sink / summary with explicit QL. See `dataflow/ExternalFlow.qll` for documentation and specification of the model format, and `models/implementations/ZMQ.qll` for a simple example of models. Importing models from `.yml` is not yet supported.

### Minor Analysis Improvements

* Source models have been added for the standard library function `getc` (and variations).
* Source, sink and flow models for the ZeroMQ (ZMQ) networking library have been added.
* Parameters of functions without definitions now have `ParameterNode`s.
* The alias analysis used internally by various libraries has been improved to answer alias questions more conservatively. As a result, some queries may report fewer false positives.

## 0.12.11

No user-facing changes.
Expand Down
4 changes: 0 additions & 4 deletions cpp/ql/lib/change-notes/2024-04-05-sound-ir.md

This file was deleted.

4 changes: 0 additions & 4 deletions cpp/ql/lib/change-notes/2024-04-18-param-nodes.md

This file was deleted.

4 changes: 0 additions & 4 deletions cpp/ql/lib/change-notes/2024-04-26-outdated-deprecations.md

This file was deleted.

4 changes: 0 additions & 4 deletions cpp/ql/lib/change-notes/2024-10-04-getc.md

This file was deleted.

4 changes: 0 additions & 4 deletions cpp/ql/lib/change-notes/2024-10-04-models-as-data.md

This file was deleted.

4 changes: 0 additions & 4 deletions cpp/ql/lib/change-notes/2024-10-04-zmq.md

This file was deleted.

16 changes: 16 additions & 0 deletions cpp/ql/lib/change-notes/released/0.13.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
## 0.13.0

### Breaking Changes

* Deleted the deprecated `GlobalValueNumberingImpl.qll` implementation.

### New Features

* Models-as-Data support has been added for C/C++. This feature allows flow sources, sinks and summaries to be expressed in compact strings as an alternative to modelling each source / sink / summary with explicit QL. See `dataflow/ExternalFlow.qll` for documentation and specification of the model format, and `models/implementations/ZMQ.qll` for a simple example of models. Importing models from `.yml` is not yet supported.

### Minor Analysis Improvements

* Source models have been added for the standard library function `getc` (and variations).
* Source, sink and flow models for the ZeroMQ (ZMQ) networking library have been added.
* Parameters of functions without definitions now have `ParameterNode`s.
* The alias analysis used internally by various libraries has been improved to answer alias questions more conservatively. As a result, some queries may report fewer false positives.
2 changes: 1 addition & 1 deletion cpp/ql/lib/codeql-pack.release.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.12.11
lastReleaseVersion: 0.13.0
2 changes: 1 addition & 1 deletion cpp/ql/lib/qlpack.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: codeql/cpp-all
version: 0.12.12-dev
version: 0.13.1-dev
groups: cpp
dbscheme: semmlecode.cpp.dbscheme
extractor: cpp
Expand Down
21 changes: 21 additions & 0 deletions cpp/ql/lib/semmle/code/cpp/controlflow/IRGuards.qll
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,27 @@ private predicate simple_comparison_eq(Instruction test, Operand op, int k, Abst
exists(switch.getSuccessor(case)) and
case.getValue().toInt() = k
)
or
// There's no implicit CompareInstruction in files compiled as C since C
// doesn't have implicit boolean conversions. So instead we check whether
// there's a branch on a value of pointer or integer type.
exists(ConditionalBranchInstruction branch, IRType type |
not test instanceof CompareInstruction and
type = test.getResultIRType() and
(type instanceof IRAddressType or type instanceof IRIntegerType) and
test = branch.getCondition() and
op.getDef() = test
|
// We'd like to also include a case such as:
// ```
// k = 1 and
// value.(BooleanValue).getValue() = true
// ```
// but all we know is that the value is non-zero in the true branch.
// So we can only conclude something in the false branch.
k = 0 and
value.(BooleanValue).getValue() = false
)
}

private predicate complex_eq(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1276,6 +1276,11 @@ class TranslatedJumpStmt extends TranslatedStmt {
override JumpStmt stmt;

override Instruction getFirstInstruction(EdgeKind kind) {
// The first instruction is a destructor call, if any.
result = this.getChildInternal(0).getFirstInstruction(kind)
or
// Otherwise, the first (and only) instruction is a `NoOp`
not exists(this.getChildInternal(0)) and
result = this.getInstruction(OnlyInstructionTag()) and
kind instanceof GotoEdge
}
Expand All @@ -1284,7 +1289,20 @@ class TranslatedJumpStmt extends TranslatedStmt {
result = this.getInstruction(OnlyInstructionTag())
}

override TranslatedElement getChildInternal(int id) { none() }
private TranslatedCall getTranslatedImplicitDestructorCall(int id) {
result.getExpr() = stmt.getImplicitDestructorCall(id)
}

override TranslatedElement getLastChild() {
result =
this.getTranslatedImplicitDestructorCall(max(int id |
exists(stmt.getImplicitDestructorCall(id))
))
}

override TranslatedElement getChildInternal(int id) {
result = this.getTranslatedImplicitDestructorCall(id)
}

override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType resultType) {
tag = OnlyInstructionTag() and
Expand All @@ -1297,7 +1315,19 @@ class TranslatedJumpStmt extends TranslatedStmt {
result = getTranslatedStmt(stmt.getTarget()).getFirstInstruction(kind)
}

override Instruction getChildSuccessorInternal(TranslatedElement child, EdgeKind kind) { none() }
final override predicate handlesDestructorsExplicitly() { any() }

override Instruction getChildSuccessorInternal(TranslatedElement child, EdgeKind kind) {
exists(int id | child = this.getChildInternal(id) |
// Transition to the next destructor call, if any.
result = this.getChildInternal(id + 1).getFirstInstruction(kind)
or
// And otherwise, exit this element by flowing to the target of the jump.
not exists(this.getChildInternal(id + 1)) and
kind instanceof GotoEdge and
result = this.getInstruction(OnlyInstructionTag())
)
}
}

private EdgeKind getCaseEdge(SwitchCase switchCase) {
Expand Down
8 changes: 8 additions & 0 deletions cpp/ql/src/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## 0.9.11

### Minor Analysis Improvements

* The "Uncontrolled data used in path expression" query (`cpp/path-injection`) query produces fewer near-duplicate results.
* The "Global variable may be used before initialization" query (`cpp/global-use-before-init`) no longer raises an alert on global variables that are initialized when they are declared.
* The "Inconsistent null check of pointer" query (`cpp/inconsistent-nullness-testing`) query no longer raises an alert when the guarded check is in a macro expansion.

## 0.9.10

No user-facing changes.
Expand Down
5 changes: 5 additions & 0 deletions cpp/ql/src/Security/CWE/CWE-022/TaintedPath.ql
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ module TaintedPathConfig implements DataFlow::ConfigSig {
hasUpperBoundsCheck(checkedVar)
)
}

predicate isBarrierOut(DataFlow::Node node) {
// make sinks barriers so that we only report the closest instance
isSink(node)
}
}

module TaintedPath = TaintTracking::Global<TaintedPathConfig>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ This is because the temporary container is not bound to a rvalue reference.
</p>
<sample src="IteratorToExpiredContainerExtendedLifetime.cpp" />

<p>
To fix <code>lifetime_of_temp_not_extended</code>, consider rewriting the code so that the lifetime of the temporary object is extended.
In <code>fixed_lifetime_of_temp_not_extended</code>, the lifetime of the temporary object has been extended by storing it in an rvalue reference.
</p>
<sample src="IteratorToExpiredContainerExtendedLifetime-fixed.cpp" />

</example>
<references>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
* @name Iterator to expired container
* @description Using an iterator owned by a container whose lifetime has expired may lead to unexpected behavior.
* @kind problem
* @precision high
* @precision medium
* @id cpp/iterator-to-expired-container
* @problem.severity warning
* @security-severity 8.8
* @tags reliability
* security
* external/cwe/cwe-416
Expand Down Expand Up @@ -61,14 +62,38 @@ DataFlow::Node getADestroyedNode(DataFlow::Node n) {
)
}

predicate destroyedToBeginSink(DataFlow::Node sink, FunctionCall fc) {
predicate destroyedToBeginSink(DataFlow::Node sink) {
exists(CallInstruction call |
call = sink.asOperand().(ThisArgumentOperand).getCall() and
fc = call.getUnconvertedResultExpression() and
call.getStaticCallTarget() instanceof BeginOrEndFunction
)
}

/**
* Holds if `node1` is the node corresponding to a qualifier of a destructor
* call and `node2` is a node that is destroyed as a result of `node1` being
* destroyed.
*/
private predicate qualifierToDestroyed(DataFlow::Node node1, DataFlow::Node node2) {
tempToDestructorSink(node1, _) and
node2 = getADestroyedNode(node1)
}

/**
* A configuration to track flow from a destroyed node to a qualifier of
* a `begin` or `end` function call.
*
* This configuration exists to prevent a cartesian product between all sinks and
* all states in `Config::isSink`.
*/
module Config0 implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { qualifierToDestroyed(_, source) }

predicate isSink(DataFlow::Node sink) { destroyedToBeginSink(sink) }
}

module Flow0 = DataFlow::Global<Config0>;

/**
* A configuration to track flow from a temporary variable to the qualifier of
* a destructor call, and subsequently to a qualifier of a call to `begin` or
Expand All @@ -78,12 +103,15 @@ module Config implements DataFlow::StateConfigSig {
newtype FlowState =
additional TempToDestructor() or
additional DestroyedToBegin(DataFlow::Node n) {
exists(DataFlow::Node thisOperand |
tempToDestructorSink(thisOperand, _) and
n = getADestroyedNode(thisOperand)
)
any(Flow0::PathNode pn | pn.isSource()).getNode() = n
}

/**
* Holds if `sink` is a qualifier to a call to `begin`, and `mid` is an
* object that is destroyed.
*/
private predicate relevant(DataFlow::Node mid, DataFlow::Node sink) { Flow0::flow(mid, sink) }

predicate isSource(DataFlow::Node source, FlowState state) {
source.asInstruction().(VariableAddressInstruction).getIRVariable() instanceof IRTempVariable and
state = TempToDestructor()
Expand All @@ -92,16 +120,16 @@ module Config implements DataFlow::StateConfigSig {
predicate isAdditionalFlowStep(
DataFlow::Node node1, FlowState state1, DataFlow::Node node2, FlowState state2
) {
tempToDestructorSink(node1, _) and
state1 = TempToDestructor() and
state2 = DestroyedToBegin(node2) and
node2 = getADestroyedNode(node1)
qualifierToDestroyed(node1, node2)
}

predicate isSink(DataFlow::Node sink, FlowState state) {
// Note: This is a non-trivial cartesian product!
// Hopefully, both of these sets are quite small in practice
destroyedToBeginSink(sink, _) and state instanceof DestroyedToBegin
exists(DataFlow::Node mid |
relevant(mid, sink) and
state = DestroyedToBegin(mid)
)
}

DataFlow::FlowFeature getAFeature() {
Expand All @@ -121,9 +149,9 @@ module Config implements DataFlow::StateConfigSig {

module Flow = DataFlow::GlobalWithState<Config>;

from Flow::PathNode source, Flow::PathNode sink, FunctionCall beginOrEnd, DataFlow::Node mid
from Flow::PathNode source, Flow::PathNode sink, DataFlow::Node mid
where
Flow::flowPath(source, sink) and
destroyedToBeginSink(sink.getNode(), beginOrEnd) and
destroyedToBeginSink(sink.getNode()) and
sink.getState() = Config::DestroyedToBegin(mid)
select mid, "This object is destroyed before $@ is called.", beginOrEnd, beginOrEnd.toString()
select mid, "This object is destroyed at the end of the full-expression."
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
void fixed_lifetime_of_temp_not_extended() {
auto&& v = get_vector();
for(auto x : log_and_return_argument(v)) {
use(x); // GOOD: The lifetime of the container returned by `get_vector()` has been extended to the lifetime of `v`.
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
category: newQuery
---
* Added a new query, `cpp/iterator-to-expired-container`, to detect the creation of iterators owned by a temporary objects that are about to be destroyed.
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
---
category: minorAnalysis
---
## 0.9.11

### Minor Analysis Improvements

* The "Uncontrolled data used in path expression" query (`cpp/path-injection`) query produces fewer near-duplicate results.
* The "Global variable may be used before initialization" query (`cpp/global-use-before-init`) no longer raises an alert on global variables that are initialized when they are declared.
* The "Inconsistent null check of pointer" query (`cpp/inconsistent-nullness-testing`) query no longer raises an alert when the guarded check is in a macro expansion.
* The "Inconsistent null check of pointer" query (`cpp/inconsistent-nullness-testing`) query no longer raises an alert when the guarded check is in a macro expansion.
2 changes: 1 addition & 1 deletion cpp/ql/src/codeql-pack.release.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.9.10
lastReleaseVersion: 0.9.11
11 changes: 11 additions & 0 deletions cpp/ql/src/experimental/Best Practices/GuardedFree.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
void test()
{
char *foo = malloc(100);

// BAD
if (foo)
free(foo);

// GOOD
free(foo);
}
18 changes: 18 additions & 0 deletions cpp/ql/src/experimental/Best Practices/GuardedFree.qhelp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!DOCTYPE qhelp SYSTEM "qhelp.dtd">
<qhelp>
<overview>
<p>The <code>free</code> function, which deallocates heap memory, may accept a NULL pointer and take no action. Therefore, it is unnecessary to check its argument for the value of NULL before a function call to <code>free</code>. As such, these guards may hinder performance and readability.</p>
</overview>
<recommendation>
<p>A function call to <code>free</code> should not depend upon the value of its argument. Delete the <code>if</code> condition preceeding a function call to <code>free</code> when its only purpose is to check the value of the pointer to be freed.</p>
</recommendation>
<example>
<sample src = "GuardedFree.cpp" />
</example>
<references>
<li>
The Open Group Base Specifications Issue 7, 2018 Edition:
<a href="https://pubs.opengroup.org/onlinepubs/9699919799/functions/free.html">free - free allocated memory</a>
</li>
</references>
</qhelp>
26 changes: 26 additions & 0 deletions cpp/ql/src/experimental/Best Practices/GuardedFree.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* @name Guarded Free
* @description NULL-condition guards before function calls to the memory-deallocation
* function free(3) are unnecessary, because passing NULL to free(3) is a no-op.
* @kind problem
* @problem.severity recommendation
* @precision very-high
* @id cpp/guarded-free
* @tags maintainability
* readability
* experimental
*/

import cpp
import semmle.code.cpp.controlflow.Guards

class FreeCall extends FunctionCall {
FreeCall() { this.getTarget().hasGlobalName("free") }
}

from GuardCondition gc, FreeCall fc, Variable v, BasicBlock bb
where
gc.ensuresEq(v.getAnAccess(), 0, bb, false) and
fc.getArgument(0) = v.getAnAccess() and
bb = fc.getEnclosingStmt()
select gc, "unnecessary NULL check before call to $@", fc, "free"
Loading

0 comments on commit a8d3226

Please sign in to comment.