Skip to content

Commit b38552c

Browse files
committed
EssentialTypes: Implement Rule 21.14
Adds a query to detect the use of memcmp to compare null-terminated strings, using global data flow from hard-coded string literals or array literals.
1 parent 3eca5d6 commit b38552c

File tree

7 files changed

+181
-1
lines changed

7 files changed

+181
-1
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* @id c/misra/memcmp-used-to-compare-null-terminated-strings
3+
* @name RULE-21-14: The Standard Library function memcmp shall not be used to compare null terminated strings
4+
* @description Using memcmp to compare null terminated strings may give unexpected results because
5+
* memcmp compares by size with no consideration for the null terminator.
6+
* @kind path-problem
7+
* @precision very-high
8+
* @problem.severity error
9+
* @tags external/misra/id/rule-21-14
10+
* maintainability
11+
* correctness
12+
* external/misra/obligation/required
13+
*/
14+
15+
import cpp
16+
import codingstandards.c.misra
17+
import codingstandards.c.misra.EssentialTypes
18+
import semmle.code.cpp.dataflow.TaintTracking
19+
import DataFlow::PathGraph
20+
21+
// Data flow from a StringLiteral or from an array of characters, to a memcmp call
22+
class NullTerminatedStringToMemcmpConfiguration extends TaintTracking::Configuration {
23+
NullTerminatedStringToMemcmpConfiguration() { this = "NullTerminatedStringToMemcmpConfiguration" }
24+
25+
override predicate isSource(DataFlow::Node source) {
26+
source.asExpr() instanceof StringLiteral
27+
or
28+
exists(Variable v, ArrayAggregateLiteral aal |
29+
aal = v.getInitializer().getExpr() and
30+
// The array element type is an essentially character type
31+
getEssentialTypeCategory(aal.getElementType()) = EssentiallyCharacterType() and
32+
// Includes a null terminator somewhere in the array initializer
33+
aal.getElementExpr(_).getValue().toInt() = 0
34+
|
35+
// For local variables, use the array aggregate literal as the source
36+
aal = source.asExpr()
37+
or
38+
// ArrayAggregateLiterals used as initializers for global variables are not viable sources
39+
// for global data flow, so we instead report variable accesses as sources, where the variable
40+
// is constant or is not assigned in the program
41+
v instanceof GlobalVariable and
42+
source.asExpr() = v.getAnAccess() and
43+
(
44+
v.isConst()
45+
or
46+
not exists(Expr e | e = v.getAnAssignedValue() and not e = aal)
47+
)
48+
)
49+
}
50+
51+
override predicate isSink(DataFlow::Node sink) {
52+
exists(FunctionCall memcmp |
53+
memcmp.getTarget().hasGlobalOrStdName("memcmp") and
54+
sink.asExpr() = memcmp.getArgument([0, 1])
55+
)
56+
}
57+
}
58+
59+
from
60+
FunctionCall memcmp, DataFlow::PathNode source, DataFlow::PathNode sink,
61+
DataFlow::PathNode source1, DataFlow::PathNode arg1, DataFlow::PathNode source2,
62+
DataFlow::PathNode arg2
63+
where
64+
not isExcluded(memcmp, EssentialTypesPackage::memcmpUsedToCompareNullTerminatedStringsQuery()) and
65+
memcmp.getTarget().hasGlobalOrStdName("memcmp") and
66+
arg1.getNode().asExpr() = memcmp.getArgument(0) and
67+
arg2.getNode().asExpr() = memcmp.getArgument(1) and
68+
// There is a path from a null-terminated string to each argument
69+
exists(NullTerminatedStringToMemcmpConfiguration cfg |
70+
cfg.hasFlowPath(source1, arg1) and
71+
cfg.hasFlowPath(source2, arg2)
72+
) and
73+
// Produce multiple paths for each result, one for each source/arg pair
74+
(
75+
source = source1 and sink = arg1
76+
or
77+
source = source2 and sink = arg2
78+
)
79+
select memcmp, source, sink, "memcmp used to compare $@ with $@.", source1,
80+
"null-terminated string", source2, "null-terminated string"
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
edges
2+
| test.c:12:13:12:15 | a | test.c:14:10:14:10 | a |
3+
| test.c:12:13:12:15 | a | test.c:23:13:23:13 | a |
4+
| test.c:12:13:12:15 | a | test.c:24:10:24:10 | a |
5+
| test.c:13:13:13:15 | b | test.c:14:13:14:13 | b |
6+
| test.c:18:15:18:28 | {...} | test.c:21:10:21:10 | e |
7+
| test.c:19:15:19:28 | {...} | test.c:21:13:21:13 | f |
8+
nodes
9+
| test.c:10:10:10:12 | a | semmle.label | a |
10+
| test.c:10:15:10:17 | b | semmle.label | b |
11+
| test.c:12:13:12:15 | a | semmle.label | a |
12+
| test.c:13:13:13:15 | b | semmle.label | b |
13+
| test.c:14:10:14:10 | a | semmle.label | a |
14+
| test.c:14:13:14:13 | b | semmle.label | b |
15+
| test.c:16:10:16:10 | c | semmle.label | c |
16+
| test.c:16:13:16:13 | d | semmle.label | d |
17+
| test.c:18:15:18:28 | {...} | semmle.label | {...} |
18+
| test.c:19:15:19:28 | {...} | semmle.label | {...} |
19+
| test.c:21:10:21:10 | e | semmle.label | e |
20+
| test.c:21:13:21:13 | f | semmle.label | f |
21+
| test.c:23:13:23:13 | a | semmle.label | a |
22+
| test.c:24:10:24:10 | a | semmle.label | a |
23+
| test.c:26:13:26:13 | c | semmle.label | c |
24+
| test.c:27:10:27:10 | c | semmle.label | c |
25+
subpaths
26+
#select
27+
| test.c:10:3:10:8 | call to memcmp | test.c:10:10:10:12 | a | test.c:10:10:10:12 | a | memcmp used to compare $@ with $@. | test.c:10:10:10:12 | a | null-terminated string | test.c:10:15:10:17 | b | null-terminated string |
28+
| test.c:10:3:10:8 | call to memcmp | test.c:10:15:10:17 | b | test.c:10:15:10:17 | b | memcmp used to compare $@ with $@. | test.c:10:10:10:12 | a | null-terminated string | test.c:10:15:10:17 | b | null-terminated string |
29+
| test.c:14:3:14:8 | call to memcmp | test.c:12:13:12:15 | a | test.c:14:10:14:10 | a | memcmp used to compare $@ with $@. | test.c:12:13:12:15 | a | null-terminated string | test.c:13:13:13:15 | b | null-terminated string |
30+
| test.c:14:3:14:8 | call to memcmp | test.c:13:13:13:15 | b | test.c:14:13:14:13 | b | memcmp used to compare $@ with $@. | test.c:12:13:12:15 | a | null-terminated string | test.c:13:13:13:15 | b | null-terminated string |
31+
| test.c:16:3:16:8 | call to memcmp | test.c:16:10:16:10 | c | test.c:16:10:16:10 | c | memcmp used to compare $@ with $@. | test.c:16:10:16:10 | c | null-terminated string | test.c:16:13:16:13 | d | null-terminated string |
32+
| test.c:16:3:16:8 | call to memcmp | test.c:16:13:16:13 | d | test.c:16:13:16:13 | d | memcmp used to compare $@ with $@. | test.c:16:10:16:10 | c | null-terminated string | test.c:16:13:16:13 | d | null-terminated string |
33+
| test.c:21:3:21:8 | call to memcmp | test.c:18:15:18:28 | {...} | test.c:21:10:21:10 | e | memcmp used to compare $@ with $@. | test.c:18:15:18:28 | {...} | null-terminated string | test.c:19:15:19:28 | {...} | null-terminated string |
34+
| test.c:21:3:21:8 | call to memcmp | test.c:19:15:19:28 | {...} | test.c:21:13:21:13 | f | memcmp used to compare $@ with $@. | test.c:18:15:18:28 | {...} | null-terminated string | test.c:19:15:19:28 | {...} | null-terminated string |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
rules/RULE-21-14/MemcmpUsedToCompareNullTerminatedStrings.ql

c/misra/test/rules/RULE-21-14/test.c

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#include <string.h>
2+
3+
extern char a[10];
4+
extern char b[10];
5+
6+
char c[10] = {'a', 'b', 0};
7+
char d[10] = {'a', 'b', 0};
8+
9+
void testCmp(int *p) {
10+
memcmp("a", "b", 1); // NON_COMPLIANT
11+
12+
strcpy(a, "a");
13+
strcpy(b, "b");
14+
memcmp(a, b, 1); // NON_COMPLIANT
15+
16+
memcmp(c, d, 1); // NON_COMPLIANT
17+
18+
char e[10] = {'a', 'b', 0};
19+
char f[10] = {'a', 'b', 0};
20+
21+
memcmp(e, f, 1); // NON_COMPLIANT
22+
23+
memcmp(p, a, 1); // COMPLIANT
24+
memcmp(a, p, 1); // COMPLIANT
25+
26+
memcmp(p, c, 1); // COMPLIANT
27+
memcmp(c, p, 1); // COMPLIANT
28+
}

cpp/common/src/codingstandards/cpp/exclusions/c/EssentialTypes.qll

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ newtype EssentialTypesQuery =
1414
TImplicitConversionOfCompositeExpressionQuery() or
1515
TInappropriateCastOfCompositeExpressionQuery() or
1616
TLoopOverEssentiallyFloatTypeQuery() or
17+
TMemcmpUsedToCompareNullTerminatedStringsQuery() or
1718
TMemcmpOnInappropriateEssentialTypeArgsQuery()
1819

1920
predicate isEssentialTypesQueryMetadata(Query query, string queryId, string ruleId, string category) {
@@ -107,6 +108,15 @@ predicate isEssentialTypesQueryMetadata(Query query, string queryId, string rule
107108
ruleId = "RULE-14-1" and
108109
category = "required"
109110
or
111+
query =
112+
// `Query` instance for the `memcmpUsedToCompareNullTerminatedStrings` query
113+
EssentialTypesPackage::memcmpUsedToCompareNullTerminatedStringsQuery() and
114+
queryId =
115+
// `@id` for the `memcmpUsedToCompareNullTerminatedStrings` query
116+
"c/misra/memcmp-used-to-compare-null-terminated-strings" and
117+
ruleId = "RULE-21-14" and
118+
category = "required"
119+
or
110120
query =
111121
// `Query` instance for the `memcmpOnInappropriateEssentialTypeArgs` query
112122
EssentialTypesPackage::memcmpOnInappropriateEssentialTypeArgsQuery() and
@@ -188,6 +198,13 @@ module EssentialTypesPackage {
188198
TQueryC(TEssentialTypesPackageQuery(TLoopOverEssentiallyFloatTypeQuery()))
189199
}
190200

201+
Query memcmpUsedToCompareNullTerminatedStringsQuery() {
202+
//autogenerate `Query` type
203+
result =
204+
// `Query` type for `memcmpUsedToCompareNullTerminatedStrings` query
205+
TQueryC(TEssentialTypesPackageQuery(TMemcmpUsedToCompareNullTerminatedStringsQuery()))
206+
}
207+
191208
Query memcmpOnInappropriateEssentialTypeArgsQuery() {
192209
//autogenerate `Query` type
193210
result =

rule_packages/c/EssentialTypes.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,26 @@
190190
],
191191
"title": "A loop counter shall not have essentially floating type"
192192
},
193+
"RULE-21-14": {
194+
"properties": {
195+
"obligation": "required"
196+
},
197+
"queries": [
198+
{
199+
"description": "Using memcmp to compare null terminated strings may give unexpected results because memcmp compares by size with no consideration for the null terminator.",
200+
"kind": "path-problem",
201+
"name": "The Standard Library function memcmp shall not be used to compare null terminated strings",
202+
"precision": "very-high",
203+
"severity": "error",
204+
"short_name": "MemcmpUsedToCompareNullTerminatedStrings",
205+
"tags": [
206+
"maintainability",
207+
"correctness"
208+
]
209+
}
210+
],
211+
"title": "The Standard Library function memcmp shall not be used to compare null terminated strings"
212+
},
193213
"RULE-21-16": {
194214
"properties": {
195215
"obligation": "required"

rules.csv

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -756,7 +756,7 @@ c,MISRA-C-2012,RULE-21-10,Yes,Required,,,The Standard Library time and date func
756756
c,MISRA-C-2012,RULE-21-11,Yes,Required,,,The standard header file <tgmath.h> shall not be used,,Banned,Easy,
757757
c,MISRA-C-2012,RULE-21-12,Yes,Advisory,,,The exception handling features of <fenv.h> should not be used,,Banned,Easy,
758758
c,MISRA-C-2012,RULE-21-13,Yes,Mandatory,,,Any value passed to a function in <ctype.h> shall be representable as an unsigned char or be the value EOF,,Types,Medium,
759-
c,MISRA-C-2012,RULE-21-14,Yes,Required,,,The Standard Library function memcmp shall not be used to compare null terminated strings,,Types,Hard,
759+
c,MISRA-C-2012,RULE-21-14,Yes,Required,,,The Standard Library function memcmp shall not be used to compare null terminated strings,,EssentialTypes,Hard,
760760
c,MISRA-C-2012,RULE-21-15,Yes,Required,,,"The pointer arguments to the Standard Library functions memcpy, memmove and memcmp shall be pointers to qualified or unqualified versions of compatible types",,Types,Medium,
761761
c,MISRA-C-2012,RULE-21-16,Yes,Required,,,"The pointer arguments to the Standard Library function memcmp shall point to either a pointer type, an essentially signed type, an essentially unsigned type, an essentially Boolean type or an essentially enum type",,EssentialTypes,Medium,
762762
c,MISRA-C-2012,RULE-21-17,Yes,Mandatory,,,Use of the string handling functions from <string.h> shall not result in accesses beyond the bounds of the objects referenced by their pointer parameters,,Memory2,Hard,

0 commit comments

Comments
 (0)