Skip to content

Commit c388d2d

Browse files
authored
Merge pull request #579 from knewbury01/knewbury01/fix-31
STR32-C: reduce false negative related to realloc model
2 parents 54d2ee8 + 31d2f57 commit c388d2d

File tree

5 files changed

+156
-46
lines changed

5 files changed

+156
-46
lines changed

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

+81-22
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"
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

+37-1
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+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- `STR32-C` - `NonNullTerminatedToFunctionThatExpectsAString.ql`:
2+
- Fixes #31. Realloc was not modelled previously.

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

+14-7
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)