Skip to content

Commit e073f4a

Browse files
author
Nikita Kraiouchkine
committed
MEM34-C and RULE-22-2: Reduce FPs
The query implementation is now a path-problem and only outputs results for flow from address-of expressions and global variable accesses that do not have allocation expressions assigned to them.
1 parent 47eaf7c commit e073f4a

File tree

7 files changed

+89
-64
lines changed

7 files changed

+89
-64
lines changed

c/cert/src/rules/MEM34-C/OnlyFreeMemoryAllocatedDynamicallyCert.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* @name MEM34-C: Only free memory allocated dynamically
44
* @description Freeing memory that is not allocated dynamically can lead to heap corruption and
55
* undefined behavior.
6-
* @kind problem
6+
* @kind path-problem
77
* @precision high
88
* @problem.severity error
99
* @tags external/cert/id/mem34-c
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,24 @@
1-
| test.c:8:8:8:10 | g_p | Free expression frees non-dynamically allocated memory. | test.c:8:8:8:10 | g_p | |
2-
| test.c:10:8:10:10 | g_p | Free expression frees $@ which was not dynamically allocated. | test.c:9:9:9:12 | & ... | memory |
3-
| test.c:15:33:15:35 | g_p | Free expression frees non-dynamically allocated memory. | test.c:15:33:15:35 | g_p | |
4-
| test.c:17:36:17:38 | ptr | Free expression frees $@ which was not dynamically allocated. | test.c:24:7:24:8 | & ... | memory |
5-
| test.c:23:8:23:8 | p | Free expression frees $@ which was not dynamically allocated. | test.c:22:13:22:14 | & ... | memory |
6-
| test.c:42:10:42:10 | p | Free expression frees non-dynamically allocated memory. | test.c:42:10:42:10 | p | |
1+
problems
2+
| test.c:8:8:8:10 | g_p | test.c:8:8:8:10 | g_p | test.c:8:8:8:10 | g_p | Free expression frees memory which was not dynamically allocated. |
3+
| test.c:10:8:10:10 | g_p | test.c:10:8:10:10 | g_p | test.c:10:8:10:10 | g_p | Free expression frees memory which was not dynamically allocated. |
4+
| test.c:12:8:12:10 | g_p | test.c:12:8:12:10 | g_p | test.c:12:8:12:10 | g_p | Free expression frees memory which was not dynamically allocated. |
5+
| test.c:16:33:16:35 | g_p | test.c:16:33:16:35 | g_p | test.c:16:33:16:35 | g_p | Free expression frees memory which was not dynamically allocated. |
6+
| test.c:18:36:18:38 | ptr | test.c:27:7:27:8 | & ... | test.c:18:36:18:38 | ptr | Free expression frees memory which was not dynamically allocated. |
7+
| test.c:26:8:26:8 | p | test.c:25:13:25:14 | & ... | test.c:26:8:26:8 | p | Free expression frees memory which was not dynamically allocated. |
8+
edges
9+
| test.c:18:24:18:26 | ptr | test.c:18:36:18:38 | ptr |
10+
| test.c:25:13:25:14 | & ... | test.c:26:8:26:8 | p |
11+
| test.c:27:7:27:8 | & ... | test.c:28:15:28:15 | p |
12+
| test.c:28:15:28:15 | p | test.c:18:24:18:26 | ptr |
13+
nodes
14+
| test.c:8:8:8:10 | g_p | semmle.label | g_p |
15+
| test.c:10:8:10:10 | g_p | semmle.label | g_p |
16+
| test.c:12:8:12:10 | g_p | semmle.label | g_p |
17+
| test.c:16:33:16:35 | g_p | semmle.label | g_p |
18+
| test.c:18:24:18:26 | ptr | semmle.label | ptr |
19+
| test.c:18:36:18:38 | ptr | semmle.label | ptr |
20+
| test.c:25:13:25:14 | & ... | semmle.label | & ... |
21+
| test.c:26:8:26:8 | p | semmle.label | p |
22+
| test.c:27:7:27:8 | & ... | semmle.label | & ... |
23+
| test.c:28:15:28:15 | p | semmle.label | p |
24+
subpaths

c/common/test/rules/onlyfreememoryallocateddynamicallyshared/test.c

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,29 @@ void test_global(void) {
99
g_p = &g_i;
1010
free(g_p); // NON_COMPLIANT
1111
g_p = malloc(10);
12-
free(g_p); // COMPLIANT - but could be written to in different scope
12+
free(g_p); // COMPLIANT[FALSE_POSITIVE] - but could be written to in different
13+
// scope
1314
}
1415

1516
void test_global_b(void) { free(g_p); } // NON_COMPLIANT
1617

1718
void free_nested(void *ptr) { free(ptr); } // NON_COMPLIANT - some paths
1819

20+
void get_allocated_memory(void **p) { *p = malloc(10); }
21+
1922
void test_local(void) {
2023
int i;
2124
int j;
2225
void *p = &i;
2326
free(p); // NON_COMPLIANT
2427
p = &j;
25-
free_nested(p); // NON_COMPLIANT
28+
free_nested(p); // NON_COMPLIANT - reported on line 18
2629
p = malloc(10);
2730
free(p); // COMPLIANT
2831
p = malloc(10);
2932
free_nested(p); // COMPLIANT
33+
get_allocated_memory(&p);
34+
free(p); // COMPLIANT
3035
}
3136

3237
struct S {
@@ -39,7 +44,7 @@ void test_local_field_nested(struct S *s) { free(s->p); } // COMPLIANT
3944
void test_local_field(void) {
4045
struct S s;
4146
s.p = &s.i;
42-
free(s.p); // NON_COMPLIANT
47+
free(s.p); // NON_COMPLIANT[FALSE_NEGATIVE]
4348
s.p = malloc(10);
4449
free(s.p); // COMPLIANT
4550
s.p = malloc(10);

c/misra/src/rules/RULE-22-2/OnlyFreeMemoryAllocatedDynamicallyMisra.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* @name RULE-22-2: A block of memory shall only be freed if it was allocated by means of a Standard Library function
44
* @description Freeing memory that is not allocated dynamically can lead to heap corruption and
55
* undefined behavior.
6-
* @kind problem
6+
* @kind path-problem
77
* @precision high
88
* @problem.severity error
99
* @tags external/misra/id/rule-22-2

cpp/common/src/codingstandards/cpp/rules/freememorywhennolongerneededshared/FreeMemoryWhenNoLongerNeededShared.qll

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,11 @@ import semmle.code.cpp.pointsto.PointsTo
1212

1313
predicate allocated(FunctionCall fc) { allocExpr(fc, _) }
1414

15-
/** Holds if there exists a call to a function that might free the allocation specified by `e`. */
16-
predicate freed(Expr e) {
17-
freeExpr(_, e, _) or
18-
exists(ExprCall c |
19-
// cautiously assume that any ExprCall could be a call to free.
20-
c.getAnArgument() = e
21-
)
22-
}
23-
2415
/** An expression for which there exists a function call that might free it. */
2516
class FreedExpr extends PointsToExpr {
26-
FreedExpr() { freed(this) }
17+
FreedExpr() { freeExprOrIndirect(this, _, _) }
2718

28-
override predicate interesting() { freed(this) }
19+
override predicate interesting() { freeExprOrIndirect(this, _, _) }
2920
}
3021

3122
/**

cpp/common/src/codingstandards/cpp/rules/onlyfreememoryallocateddynamicallyshared/OnlyFreeMemoryAllocatedDynamicallyShared.qll

Lines changed: 51 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import codingstandards.cpp.Exclusions
99
import codingstandards.cpp.Allocations
1010
import semmle.code.cpp.dataflow.DataFlow
1111
import semmle.code.cpp.dataflow.DataFlow2
12+
import DataFlow::PathGraph
1213

1314
/**
1415
* A pointer to potentially dynamically allocated memory
@@ -38,41 +39,54 @@ class FreeExprSink extends DataFlow::Node {
3839
}
3940

4041
/**
41-
* A data-flow configuration that tracks flow from an `AllocExprSource` to
42-
* the value assigned to a variable.
42+
* An `Expr` that is an `AddressOfExpr` of a `Variable`.
43+
*
44+
* `Field`s of `PointerType` are not included in order to reduce false-positives,
45+
* as the data-flow library sometimes equates pointers to their underlying data.
4346
*/
44-
class AllocExprSourceToAssignedValueConfig extends DataFlow2::Configuration {
45-
AllocExprSourceToAssignedValueConfig() { this = "AllocExprSourceToAssignedValueConfig" }
46-
47-
override predicate isSource(DataFlow::Node source) { source instanceof AllocExprSource }
48-
49-
override predicate isSink(DataFlow::Node sink) {
50-
sink.asExpr() = any(Variable v).getAnAssignedValue()
51-
}
52-
}
53-
54-
/**
55-
* An assignment of a value that is not a dynamically allocated pointer to a variable.
56-
*/
57-
class NonDynamicallyAllocatedVariableAssignment extends DataFlow::Node {
58-
NonDynamicallyAllocatedVariableAssignment() {
59-
exists(Variable v |
60-
this.asExpr() = v.getAnAssignedValue() and
61-
not this.asExpr() instanceof NullValue and
62-
not any(AllocExprSourceToAssignedValueConfig cfg).hasFlowTo(this)
63-
)
47+
class AddressOfExprSourceNode extends Expr {
48+
AddressOfExprSourceNode() {
49+
exists(VariableAccess va |
50+
this.(AddressOfExpr).getOperand() = va and
51+
(
52+
va.getTarget() instanceof StackVariable or
53+
va.getTarget() instanceof GlobalVariable or
54+
// allow address-of field, but only if that field is not a pointer type,
55+
// as there may be nested allocations assigned to fields of pointer types.
56+
va.(FieldAccess).getTarget().getUnderlyingType() instanceof ArithmeticType
57+
)
58+
or
59+
this = va and
60+
exists(GlobalVariable gv |
61+
gv = va.getTarget() and
62+
(
63+
gv.getUnderlyingType() instanceof ArithmeticType or
64+
not exists(gv.getAnAssignedValue()) or
65+
exists(AddressOfExprSourceNode other |
66+
DataFlow::localExprFlow(other, gv.getAnAssignedValue())
67+
)
68+
)
69+
)
70+
) and
71+
// exclude alloc(&allocated_ptr) cases
72+
not any(DynamicMemoryAllocationToAddressOfDefiningArgConfig cfg)
73+
.hasFlowTo(DataFlow::definitionByReferenceNodeFromArgument(this))
6474
}
6575
}
6676

6777
/**
6878
* A data-flow configuration that tracks flow from an `AllocExprSource` to a `FreeExprSink`.
6979
*/
70-
class DynamicMemoryAllocationToFreeConfig extends DataFlow::Configuration {
71-
DynamicMemoryAllocationToFreeConfig() { this = "DynamicMemoryAllocationToFreeConfig" }
80+
class DynamicMemoryAllocationToAddressOfDefiningArgConfig extends DataFlow2::Configuration {
81+
DynamicMemoryAllocationToAddressOfDefiningArgConfig() {
82+
this = "DynamicMemoryAllocationToAddressOfDefiningArgConfig"
83+
}
7284

7385
override predicate isSource(DataFlow::Node source) { source instanceof AllocExprSource }
7486

75-
override predicate isSink(DataFlow::Node sink) { sink instanceof FreeExprSink }
87+
override predicate isSink(DataFlow::Node sink) {
88+
sink.asDefiningArgument() instanceof AddressOfExpr
89+
}
7690
}
7791

7892
/**
@@ -83,14 +97,14 @@ class NonDynamicPointerToFreeConfig extends DataFlow::Configuration {
8397
NonDynamicPointerToFreeConfig() { this = "NonDynamicPointerToFreeConfig" }
8498

8599
override predicate isSource(DataFlow::Node source) {
86-
source instanceof NonDynamicallyAllocatedVariableAssignment
100+
source.asExpr() instanceof AddressOfExprSourceNode
87101
}
88102

89103
override predicate isSink(DataFlow::Node sink) { sink instanceof FreeExprSink }
90104

91105
override predicate isBarrierOut(DataFlow::Node node) {
92106
// the default interprocedural data-flow model flows through any field or array assignment
93-
// expressionsto the qualifier (array base, pointer dereferenced, or qualifier) instead of the
107+
// expressions to the qualifier (array base, pointer dereferenced, or qualifier) instead of the
94108
// individual element or field that the assignment modifies. this default behaviour causes
95109
// false positives for future frees of the object base, so we remove the edges
96110
// between those assignments from the graph with `isBarrierOut`.
@@ -103,25 +117,22 @@ class NonDynamicPointerToFreeConfig extends DataFlow::Configuration {
103117
)
104118
)
105119
}
120+
121+
override predicate isBarrierIn(DataFlow::Node node) {
122+
// only the last source expression is relevant
123+
isSource(node)
124+
}
106125
}
107126

108127
abstract class OnlyFreeMemoryAllocatedDynamicallySharedSharedQuery extends Query { }
109128

110129
Query getQuery() { result instanceof OnlyFreeMemoryAllocatedDynamicallySharedSharedQuery }
111130

112131
query predicate problems(
113-
FreeExprSink free, string message, DataFlow::Node source, string sourceDescription
132+
DataFlow::PathNode element, DataFlow::PathNode source, DataFlow::PathNode sink, string message
114133
) {
115-
not isExcluded(free.asExpr(), getQuery()) and
116-
(
117-
not any(DynamicMemoryAllocationToFreeConfig cfg).hasFlowTo(free) and
118-
not any(NonDynamicPointerToFreeConfig cfg).hasFlowTo(free) and
119-
message = "Free expression frees non-dynamically allocated memory." and
120-
source = free and
121-
sourceDescription = ""
122-
or
123-
any(NonDynamicPointerToFreeConfig cfg).hasFlow(source, free) and
124-
message = "Free expression frees $@ which was not dynamically allocated." and
125-
sourceDescription = "memory"
126-
)
134+
not isExcluded(element.getNode().asExpr(), getQuery()) and
135+
element = sink and
136+
any(NonDynamicPointerToFreeConfig cfg).hasFlowPath(source, sink) and
137+
message = "Free expression frees memory which was not dynamically allocated."
127138
}

rule_packages/c/Memory2.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@
113113
"queries": [
114114
{
115115
"description": "Freeing memory that is not allocated dynamically can lead to heap corruption and undefined behavior.",
116-
"kind": "problem",
116+
"kind": "path-problem",
117117
"name": "Only free memory allocated dynamically",
118118
"precision": "high",
119119
"severity": "error",
@@ -196,7 +196,7 @@
196196
"queries": [
197197
{
198198
"description": "Freeing memory that is not allocated dynamically can lead to heap corruption and undefined behavior.",
199-
"kind": "problem",
199+
"kind": "path-problem",
200200
"name": "A block of memory shall only be freed if it was allocated by means of a Standard Library function",
201201
"precision": "high",
202202
"severity": "error",

0 commit comments

Comments
 (0)