Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 6a08040

Browse filesBrowse files
committed
STR32-C: reduce false negative related to realloc model
1 parent cdaffa0 commit 6a08040
Copy full SHA for 6a08040

File tree

Expand file treeCollapse file tree

5 files changed

+156
-46
lines changed
Filter options
Expand file treeCollapse file tree

5 files changed

+156
-46
lines changed

‎c/cert/src/rules/STR32-C/NonNullTerminatedToFunctionThatExpectsAString.ql

Copy file name to clipboardExpand all lines: c/cert/src/rules/STR32-C/NonNullTerminatedToFunctionThatExpectsAString.ql
+81-22Lines changed: 81 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import codingstandards.c.cert
1717
import codingstandards.cpp.Naming
1818
import codingstandards.cpp.dataflow.TaintTracking
1919
import codingstandards.cpp.PossiblyUnsafeStringOperation
20+
import semmle.code.cpp.valuenumbering.GlobalValueNumbering
2021

2122
/**
2223
* Models a function that is part of the standard library that expects a
@@ -43,32 +44,90 @@ class ExpectsNullTerminatedStringAsArgumentFunctionCall extends FunctionCall {
4344
Expr getAnExpectingExpr() { result = e }
4445
}
4546

46-
from ExpectsNullTerminatedStringAsArgumentFunctionCall fc, Expr e, Expr target
47-
where
48-
target = fc.getAnExpectingExpr() and
49-
not isExcluded(fc, Strings1Package::nonNullTerminatedToFunctionThatExpectsAStringQuery()) and
50-
(
51-
exists(PossiblyUnsafeStringOperation op |
52-
// don't report violations of the same function call.
53-
not op = fc and
54-
e = op and
55-
TaintTracking::localTaint(DataFlow::exprNode(op.getAnArgument()), DataFlow::exprNode(target))
47+
class PossiblyUnsafeStringOperationSource extends Source {
48+
PossiblyUnsafeStringOperation op;
49+
50+
PossiblyUnsafeStringOperationSource() { this.asExpr() = op.getAnArgument() }
51+
52+
PossiblyUnsafeStringOperation getOp() { result = op }
53+
}
54+
55+
class CharArraySource extends Source {
56+
CharArrayInitializedWithStringLiteral op;
57+
58+
CharArraySource() {
59+
op.getContainerLength() <= op.getStringLiteralLength() and
60+
this.asExpr() = op
61+
}
62+
}
63+
64+
abstract class Source extends DataFlow::Node { }
65+
66+
class Sink extends DataFlow::Node {
67+
Sink() {
68+
exists(ExpectsNullTerminatedStringAsArgumentFunctionCall fc |
69+
fc.getAnExpectingExpr() = this.asExpr()
5670
)
57-
or
58-
exists(CharArrayInitializedWithStringLiteral op |
59-
e = op and
60-
op.getContainerLength() <= op.getStringLiteralLength() and
61-
TaintTracking::localTaint(DataFlow::exprNode(op), DataFlow::exprNode(target))
71+
}
72+
}
73+
74+
module MyFlowConfiguration implements DataFlow::ConfigSig {
75+
predicate isSink(DataFlow::Node sink) {
76+
sink instanceof Sink and
77+
//don't report violations of the same function call
78+
not sink instanceof Source
79+
}
80+
81+
predicate isSource(DataFlow::Node source) { source instanceof Source }
82+
83+
predicate isAdditionalFlowStep(DataFlow::Node innode, DataFlow::Node outnode) {
84+
exists(FunctionCall realloc, ReallocFunction fn |
85+
fn.getACallToThisFunction() = realloc and
86+
realloc.getArgument(0) = innode.asExpr() and
87+
realloc = outnode.asExpr()
6288
)
63-
) and
64-
// don't report cases flowing to this node where there is a flow from a
65-
// literal assignment of a null terminator
66-
not exists(AssignExpr aexp |
89+
}
90+
}
91+
92+
class ReallocFunction extends AllocationFunction {
93+
ReallocFunction() { exists(this.getReallocPtrArg()) }
94+
}
95+
96+
/**
97+
* Determines if the string is acceptably null terminated
98+
* The only condition we accept as a guarantee to null terminate is:
99+
* `str[size_expr] = '\0';`
100+
* where we do not check the value of the `size_expr` used
101+
*/
102+
predicate isGuarded(Expr guarded, Expr source) {
103+
exists(AssignExpr aexp |
67104
aexp.getLValue() instanceof ArrayExpr and
68105
aexp.getRValue() instanceof Zero and
69-
TaintTracking::localTaint(DataFlow::exprNode(aexp.getRValue()), DataFlow::exprNode(target)) and
70-
// this must be AFTER the operation causing the non-null termination to be valid.
71-
aexp.getAPredecessor*() = e
106+
// this must be AFTER the operation causing the non-null termination
107+
aexp.getAPredecessor+() = source and
108+
//this guards anything after it
109+
aexp.getASuccessor+() = guarded and
110+
// no reallocs exist after this because they will be conservatively assumed to make the buffer smaller and remove the likliehood of this properly terminating
111+
not exists(ReallocFunction realloc, FunctionCall fn |
112+
fn = realloc.getACallToThisFunction() and
113+
globalValueNumber(aexp.getLValue().(ArrayExpr).getArrayBase()) =
114+
globalValueNumber(fn.getArgument(0)) and
115+
aexp.getASuccessor+() = fn
116+
)
72117
)
118+
}
119+
120+
module MyFlow = TaintTracking::Global<MyFlowConfiguration>;
121+
122+
from
123+
DataFlow::Node source, DataFlow::Node sink, ExpectsNullTerminatedStringAsArgumentFunctionCall fc,
124+
Expr e
125+
where
126+
MyFlow::flow(source, sink) and
127+
sink.asExpr() = fc.getAnExpectingExpr() and
128+
not isGuarded(sink.asExpr(), source.asExpr()) and
129+
if source instanceof PossiblyUnsafeStringOperationSource
130+
then e = source.(PossiblyUnsafeStringOperationSource).getOp()
131+
else e = source.asExpr()
73132
select fc, "String modified by $@ is passed to function expecting a null-terminated string.", e,
74133
"this expression"
+22-16Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
1-
| test.c:19:3:19:8 | call to printf | String modified by $@ is passed to function expecting a null-terminated string. | test.c:7:20:7:24 | Cod | this expression |
2-
| test.c:20:3:20:8 | call to printf | String modified by $@ is passed to function expecting a null-terminated string. | test.c:7:20:7:24 | Cod | this expression |
3-
| test.c:22:3:22:8 | call to printf | String modified by $@ is passed to function expecting a null-terminated string. | test.c:13:3:13:9 | call to strncpy | this expression |
4-
| test.c:23:3:23:8 | call to printf | String modified by $@ is passed to function expecting a null-terminated string. | test.c:13:3:13:9 | call to strncpy | this expression |
5-
| test.c:24:3:24:8 | call to strlen | String modified by $@ is passed to function expecting a null-terminated string. | test.c:13:3:13:9 | call to strncpy | this expression |
6-
| test.c:33:3:33:9 | call to wprintf | String modified by $@ is passed to function expecting a null-terminated string. | test.c:30:24:30:29 | Cod | this expression |
7-
| test.c:46:3:46:8 | call to printf | String modified by $@ is passed to function expecting a null-terminated string. | test.c:41:3:41:10 | call to snprintf | this expression |
8-
| test.c:47:3:47:8 | call to printf | String modified by $@ is passed to function expecting a null-terminated string. | test.c:41:3:41:10 | call to snprintf | this expression |
9-
| test.c:55:3:55:8 | call to printf | String modified by $@ is passed to function expecting a null-terminated string. | test.c:53:3:53:9 | call to strncat | this expression |
10-
| test.c:56:3:56:8 | call to printf | String modified by $@ is passed to function expecting a null-terminated string. | test.c:53:3:53:9 | call to strncat | this expression |
11-
| test.c:62:3:62:8 | call to printf | String modified by $@ is passed to function expecting a null-terminated string. | test.c:60:20:60:24 | Cod | this expression |
12-
| test.c:63:3:63:8 | call to printf | String modified by $@ is passed to function expecting a null-terminated string. | test.c:60:20:60:24 | Cod | this expression |
13-
| test.c:75:3:75:8 | call to printf | String modified by $@ is passed to function expecting a null-terminated string. | test.c:72:20:72:24 | Cod | this expression |
14-
| test.c:76:3:76:8 | call to printf | String modified by $@ is passed to function expecting a null-terminated string. | test.c:72:20:72:24 | Cod | this expression |
15-
| test.c:85:3:85:8 | call to printf | String modified by $@ is passed to function expecting a null-terminated string. | test.c:83:3:83:9 | call to strncpy | this expression |
16-
| test.c:86:3:86:8 | call to printf | String modified by $@ is passed to function expecting a null-terminated string. | test.c:83:3:83:9 | call to strncpy | this expression |
1+
| test.c:20:3:20:8 | call to printf | String modified by $@ is passed to function expecting a null-terminated string. | test.c:8:20:8:24 | Cod | this expression |
2+
| test.c:21:3:21:8 | call to printf | String modified by $@ is passed to function expecting a null-terminated string. | test.c:8:20:8:24 | Cod | this expression |
3+
| test.c:23:3:23:8 | call to printf | String modified by $@ is passed to function expecting a null-terminated string. | test.c:14:3:14:9 | call to strncpy | this expression |
4+
| test.c:24:3:24:8 | call to printf | String modified by $@ is passed to function expecting a null-terminated string. | test.c:14:3:14:9 | call to strncpy | this expression |
5+
| test.c:25:3:25:8 | call to strlen | String modified by $@ is passed to function expecting a null-terminated string. | test.c:14:3:14:9 | call to strncpy | this expression |
6+
| test.c:34:3:34:9 | call to wprintf | String modified by $@ is passed to function expecting a null-terminated string. | test.c:31:24:31:29 | Cod | this expression |
7+
| test.c:47:3:47:8 | call to printf | String modified by $@ is passed to function expecting a null-terminated string. | test.c:42:3:42:10 | call to snprintf | this expression |
8+
| test.c:48:3:48:8 | call to printf | String modified by $@ is passed to function expecting a null-terminated string. | test.c:42:3:42:10 | call to snprintf | this expression |
9+
| test.c:56:3:56:8 | call to printf | String modified by $@ is passed to function expecting a null-terminated string. | test.c:54:3:54:9 | call to strncat | this expression |
10+
| test.c:57:3:57:8 | call to printf | String modified by $@ is passed to function expecting a null-terminated string. | test.c:54:3:54:9 | call to strncat | this expression |
11+
| test.c:63:3:63:8 | call to printf | String modified by $@ is passed to function expecting a null-terminated string. | test.c:61:20:61:24 | Cod | this expression |
12+
| test.c:64:3:64:8 | call to printf | String modified by $@ is passed to function expecting a null-terminated string. | test.c:61:20:61:24 | Cod | this expression |
13+
| test.c:76:3:76:8 | call to printf | String modified by $@ is passed to function expecting a null-terminated string. | test.c:73:20:73:24 | Cod | this expression |
14+
| test.c:77:3:77:8 | call to printf | String modified by $@ is passed to function expecting a null-terminated string. | test.c:73:20:73:24 | Cod | this expression |
15+
| test.c:86:3:86:8 | call to printf | String modified by $@ is passed to function expecting a null-terminated string. | test.c:84:3:84:9 | call to strncpy | this expression |
16+
| test.c:87:3:87:8 | call to printf | String modified by $@ is passed to function expecting a null-terminated string. | test.c:84:3:84:9 | call to strncpy | this expression |
17+
| test.c:95:3:95:8 | call to printf | String modified by $@ is passed to function expecting a null-terminated string. | test.c:93:17:93:21 | Cod | this expression |
18+
| test.c:95:3:95:8 | call to printf | String modified by $@ is passed to function expecting a null-terminated string. | test.c:94:3:94:9 | call to strncpy | this expression |
19+
| test.c:98:3:98:8 | call to printf | String modified by $@ is passed to function expecting a null-terminated string. | test.c:93:17:93:21 | Cod | this expression |
20+
| test.c:98:3:98:8 | call to printf | String modified by $@ is passed to function expecting a null-terminated string. | test.c:94:3:94:9 | call to strncpy | this expression |
21+
| test.c:122:3:122:8 | call to printf | String modified by $@ is passed to function expecting a null-terminated string. | test.c:117:17:117:21 | Cod | this expression |
22+
| test.c:122:3:122:8 | call to printf | String modified by $@ is passed to function expecting a null-terminated string. | test.c:118:3:118:9 | call to strncpy | this expression |

‎c/cert/test/rules/STR32-C/test.c

Copy file name to clipboardExpand all lines: c/cert/test/rules/STR32-C/test.c
+37-1Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include <stdio.h>
2+
#include <stdlib.h>
23
#include <string.h>
34
#include <wchar.h>
45

@@ -84,4 +85,39 @@ f5() {
8485

8586
printf("%s", a1_nnt); // NON_COMPLIANT
8687
printf(a1_nnt); // NON_COMPLIANT
87-
}
88+
}
89+
90+
void test_fn_reported_in_31_simple() {
91+
char *str;
92+
str = (char *)malloc(3);
93+
char a31[3] = "Cod"; // is NOT null terminated
94+
strncpy(str, a31, 3);
95+
printf(str); // NON_COMPLIANT
96+
size_t cur_msg_size = 1024;
97+
str = realloc(str, (cur_msg_size / 2 + 1) * sizeof(char));
98+
printf(str); // NON_COMPLIANT
99+
}
100+
101+
void test_fn_reported_in_31_simple_safe() {
102+
char *str;
103+
str = (char *)malloc(3);
104+
char a31[3] = "Cod"; // is NOT null terminated
105+
strncpy(str, a31, 3);
106+
size_t cur_msg_size = 1024;
107+
size_t temp_size = cur_msg_size / 2 + 1;
108+
str = realloc(str, temp_size * sizeof(char));
109+
str[temp_size - 1] = L'\0'; // Properly null-terminate str
110+
printf(str); // COMPLIANT
111+
}
112+
113+
void test_fn_reported_in_31_simple_relloc() {
114+
char *str;
115+
size_t cur_msg_size = 1024;
116+
str = (char *)malloc(cur_msg_size);
117+
char a31[3] = "Cod"; // is NOT null terminated
118+
strncpy(str, a31, 3);
119+
str[cur_msg_size - 1] = L'\0'; // Properly null-terminate str
120+
size_t temp_size = cur_msg_size / 2 + 1;
121+
str = realloc(str, temp_size * sizeof(char));
122+
printf(str); // NON_COMPLIANT
123+
}
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- `STR32-C` - `NonNullTerminatedToFunctionThatExpectsAString.ql`:
2+
- Fixes #31. Realloc and null termination were not modelled previously.

‎cpp/common/src/codingstandards/cpp/PossiblyUnsafeStringOperation.qll

Copy file name to clipboardExpand all lines: cpp/common/src/codingstandards/cpp/PossiblyUnsafeStringOperation.qll
+14-7Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,25 @@ class PossiblyUnsafeStringOperation extends FunctionCall {
3737
bwc.getTarget() instanceof StrcatFunction
3838
or
3939
// Case 2: Consider the `strncpy(dest, src, n)` function. We do not
40-
// consider `strcpy` since it is a banned function. The behavior of
41-
// strncpy(dest, src, n) is that it will copy null terminators only if n
42-
// > sizeof(src). If `src` is null-terminated then it will be null
43-
// terminated if n >= sizeof(src). We take the conservative approach and
44-
// use strictly greater. Thus this can be violated under the condition
45-
// that n < strlen(src). Note that a buffer overflow is possible if
40+
// consider `strcpy` since it is a banned function.
41+
// We cannot know if the string is already null terminated or not and thus
42+
// the conservative assumption is that it is not
43+
// The behavior of strncpy(dest, src, n) is that if sizeof(src) < n
44+
// then it will fill remainder of dst with ‘\0’ characters
45+
// ie it is only in this case that it is guaranteed to null terminate
46+
// Otherwise, dst is not terminated
47+
// If `src` is already null-terminated then it will be null
48+
// terminated if n >= sizeof(src). but we do not assume on this.
49+
// Note that a buffer overflow is possible if
4650
// `n` is greater than sizeof(dest). The point of this query is not to
4751
// check for buffer overflows but we would certainly want to indicate
4852
// this would be a case where a string will not be null terminated.
4953
bwc.getTarget() instanceof StrcpyFunction and
5054
(
51-
(bwc.getExplicitLimit() / bwc.getCharSize()) < getBufferSize(src, _) or
55+
// n <= sizeof(src) might not null terminate
56+
(bwc.getExplicitLimit() / bwc.getCharSize()) <= getBufferSize(src, _)
57+
or
58+
// sizeof(dest) < n might not null terminate
5259
getBufferSize(dest, _) < (bwc.getExplicitLimit() / bwc.getCharSize())
5360
)
5461
or

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.