Skip to content

Commit 2c70106

Browse files
authored
Merge pull request #5009 from ihsinme/ihsinme-patch-219
CPP: add query for CWE-788 Access of memory location after the end of a buffer using strncat.
2 parents bbdd7c9 + c90dc62 commit 2c70106

File tree

6 files changed

+132
-0
lines changed

6 files changed

+132
-0
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
strncat(dest, source, sizeof(dest) - strlen(dest)); // BAD: writes a zero byte past the `dest` buffer.
3+
4+
strncat(dest, source, sizeof(dest) - strlen(dest) -1); // GOOD: Reserves space for the zero byte.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>The standard library function <code>strncat(dest, source, count)</code> appends the <code>source</code> string to the <code>dest</code> string. <code>count</code> specifies the maximum number of characters to append and must be less than the remaining space in the target buffer. Calls of the form <code> strncat (dest, source, sizeof (dest) - strlen (dest)) </code> set the third argument to one more than possible. So when the <code>dest</code> is full, the expression <code> sizeof (dest) - strlen (dest) </code> will be equal to one, and not zero as the programmer might think. Making a call of this type may result in a zero byte being written just outside the <code>dest</code> buffer.</p>
7+
8+
9+
</overview>
10+
<recommendation>
11+
12+
<p>We recommend subtracting one from the third argument. For example, replace <code>strncat(dest, source, sizeof(dest)-strlen(dest))</code> with <code>strncat(dest, source, sizeof(dest)-strlen(dest)-1)</code>.</p>
13+
14+
</recommendation>
15+
<example>
16+
<p>The following example demonstrates an erroneous and corrected use of the <code>strncat</code> function.</p>
17+
<sample src="AccessOfMemoryLocationAfterEndOfBufferUsingStrncat.c" />
18+
19+
</example>
20+
<references>
21+
22+
<li>
23+
CERT C Coding Standard:
24+
<a href="https://wiki.sei.cmu.edu/confluence/display/c/STR31-C.+Guarantee+that+storage+for+strings+has+sufficient+space+for+character+data+and+the+null+terminator">STR31-C. Guarantee that storage for strings has sufficient space for character data and the null terminator</a>.
25+
</li>
26+
<li>
27+
CERT C Coding Standard:
28+
<a href="https://wiki.sei.cmu.edu/confluence/display/c/ARR30-C.+Do+not+form+or+use+out-of-bounds+pointers+or+array+subscripts">ARR30-C. Do not form or use out-of-bounds pointers or array subscripts</a>.
29+
</li>
30+
31+
</references>
32+
</qhelp>
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/**
2+
* @name Access Of Memory Location After The End Of A Buffer Using Strncat
3+
* @description Calls of the form `strncat(dest, source, sizeof (dest) - strlen (dest))` set the third argument to one more than possible. So when `dest` is full, the expression `sizeof(dest) - strlen (dest)` will be equal to one, and not zero as the programmer might think. Making a call of this type may result in a zero byte being written just outside the `dest` buffer.
4+
* @kind problem
5+
* @id cpp/access-memory-location-after-end-buffer
6+
* @problem.severity warning
7+
* @precision medium
8+
* @tags correctness
9+
* security
10+
* external/cwe/cwe-788
11+
*/
12+
13+
import cpp
14+
import semmle.code.cpp.valuenumbering.GlobalValueNumbering
15+
16+
/**
17+
* A call to `strncat` of the form `strncat(buff, str, someExpr - strlen(buf))`, for some expression `someExpr` equal to `sizeof(buff)`.
18+
*/
19+
class WrongCallStrncat extends FunctionCall {
20+
Expr leftsomeExpr;
21+
22+
WrongCallStrncat() {
23+
this.getTarget().hasGlobalOrStdName("strncat") and
24+
// the expression of the first argument in `strncat` and `strnlen` is identical
25+
globalValueNumber(this.getArgument(0)) =
26+
globalValueNumber(this.getArgument(2).(SubExpr).getRightOperand().(StrlenCall).getStringExpr()) and
27+
// using a string constant often speaks of manually calculating the length of the required buffer.
28+
(
29+
not this.getArgument(1) instanceof StringLiteral and
30+
not this.getArgument(1) instanceof CharLiteral
31+
) and
32+
// for use in predicates
33+
leftsomeExpr = this.getArgument(2).(SubExpr).getLeftOperand()
34+
}
35+
36+
/**
37+
* Holds if the left side of the expression `someExpr` equal to `sizeof(buf)`.
38+
*/
39+
predicate isExpressionEqualSizeof() {
40+
// the left side of the expression `someExpr` is `sizeof(buf)`.
41+
globalValueNumber(this.getArgument(0)) =
42+
globalValueNumber(leftsomeExpr.(SizeofExprOperator).getExprOperand())
43+
or
44+
// value of the left side of the expression `someExpr` equal `sizeof(buf)` value, and `buf` is array.
45+
leftsomeExpr.getValue().toInt() = this.getArgument(0).getType().getSize()
46+
}
47+
48+
/**
49+
* Holds if the left side of the expression `someExpr` equal to variable containing the length of the memory allocated for the buffer.
50+
*/
51+
predicate isVariableEqualValueSizegBuffer() {
52+
// the left side of expression `someExpr` is the variable that was used in the function of allocating memory for the buffer`.
53+
exists(AllocationExpr alc |
54+
leftsomeExpr.(VariableAccess).getTarget() =
55+
alc.(FunctionCall).getArgument(0).(VariableAccess).getTarget()
56+
)
57+
}
58+
}
59+
60+
from WrongCallStrncat sc
61+
where
62+
sc.isExpressionEqualSizeof() or
63+
sc.isVariableEqualValueSizegBuffer()
64+
select sc, "if the used buffer is full, writing out of the buffer is possible"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
| test.c:4:3:4:9 | call to strncat | if the used buffer is full, writing out of the buffer is possible |
2+
| test.c:11:3:11:9 | call to strncat | if the used buffer is full, writing out of the buffer is possible |
3+
| test.c:19:3:19:9 | call to strncat | if the used buffer is full, writing out of the buffer is possible |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrncat.ql
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
void workFunction_0(char *s) {
2+
char buf[80];
3+
strncat(buf, s, sizeof(buf)-strlen(buf)-1); // GOOD
4+
strncat(buf, s, sizeof(buf)-strlen(buf)); // BAD
5+
strncat(buf, "fix", sizeof(buf)-strlen(buf)); // BAD [NOT DETECTED]
6+
}
7+
void workFunction_1(char *s) {
8+
#define MAX_SIZE 80
9+
char buf[MAX_SIZE];
10+
strncat(buf, s, MAX_SIZE-strlen(buf)-1); // GOOD
11+
strncat(buf, s, MAX_SIZE-strlen(buf)); // BAD
12+
strncat(buf, "fix", MAX_SIZE-strlen(buf)); // BAD [NOT DETECTED]
13+
}
14+
void workFunction_2_0(char *s) {
15+
char * buf;
16+
int len=80;
17+
buf = (char *) malloc(len);
18+
strncat(buf, s, len-strlen(buf)-1); // GOOD
19+
strncat(buf, s, len-strlen(buf)); // BAD
20+
strncat(buf, "fix", len-strlen(buf)); // BAD [NOT DETECTED]
21+
}
22+
void workFunction_2_1(char *s) {
23+
char * buf;
24+
int len=80;
25+
buf = (char *) malloc(len+1);
26+
strncat(buf, s, len-strlen(buf)-1); // GOOD
27+
strncat(buf, s, len-strlen(buf)); // GOOD
28+
}

0 commit comments

Comments
 (0)