Skip to content

Commit 68385df

Browse files
authored
Merge pull request #7386 from github/redsun82/cpp-overrunning-write-precision-split
C++: split `cpp/overrunning-write` into two
2 parents f2818eb + e6763c8 commit 68385df

26 files changed

+228
-125
lines changed

cpp/config/suites/security/cwe-120

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
@name Badly bounded write (CWE-120)
66
+ semmlecode-cpp-queries/Security/CWE/CWE-120/OverrunWrite.ql: /CWE/CWE-120
77
@name Potentially overrunning write (CWE-120)
8+
+ semmlecode-cpp-queries/Security/CWE/CWE-120/VeryLikelyOverrunWrite.ql: /CWE/CWE-120
9+
@name Likely overrunning write
810
+ semmlecode-cpp-queries/Security/CWE/CWE-120/OverrunWriteFloat.ql: /CWE/CWE-120
911
@name Potentially overrunning write with float to string conversion (CWE-120)
1012
+ semmlecode-cpp-queries/Best Practices/Likely Errors/OffsetUseBeforeRangeCheck.ql: /CWE/CWE-120
1113
@name Array offset used before range check (CWE-120)
1214
+ semmlecode-cpp-queries/Likely Bugs/Memory Management/UnsafeUseOfStrcat.ql: /CWE/CWE-120
13-
@name Potentially unsafe use of strcat (CWE-120)
15+
@name Potentially unsafe use of strcat (CWE-120)

cpp/ql/lib/semmle/code/cpp/commons/Printf.qll

Lines changed: 66 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,22 @@ private import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis
1010
private import semmle.code.cpp.rangeanalysis.RangeAnalysisUtils
1111

1212
private newtype TBufferWriteEstimationReason =
13-
TNoSpecifiedEstimateReason() or
13+
TUnspecifiedEstimateReason() or
1414
TTypeBoundsAnalysis() or
15+
TWidenedValueFlowAnalysis() or
1516
TValueFlowAnalysis()
1617

18+
private predicate gradeToReason(int grade, TBufferWriteEstimationReason reason) {
19+
// when combining reasons, lower grade takes precedence
20+
grade = 0 and reason = TUnspecifiedEstimateReason()
21+
or
22+
grade = 1 and reason = TTypeBoundsAnalysis()
23+
or
24+
grade = 2 and reason = TWidenedValueFlowAnalysis()
25+
or
26+
grade = 3 and reason = TValueFlowAnalysis()
27+
}
28+
1729
/**
1830
* A reason for a specific buffer write size estimate.
1931
*/
@@ -32,24 +44,24 @@ abstract class BufferWriteEstimationReason extends TBufferWriteEstimationReason
3244
* Combine estimate reasons. Used to give a reason for the size of a format string
3345
* conversion given reasons coming from its individual specifiers.
3446
*/
35-
abstract BufferWriteEstimationReason combineWith(BufferWriteEstimationReason other);
47+
BufferWriteEstimationReason combineWith(BufferWriteEstimationReason other) {
48+
exists(int grade, int otherGrade |
49+
gradeToReason(grade, this) and gradeToReason(otherGrade, other)
50+
|
51+
if otherGrade < grade then result = other else result = this
52+
)
53+
}
3654
}
3755

3856
/**
3957
* No particular reason given. This is currently used for backward compatibility so that
4058
* classes derived from BufferWrite and overriding `getMaxData/0` still work with the
4159
* queries as intended.
4260
*/
43-
class NoSpecifiedEstimateReason extends BufferWriteEstimationReason, TNoSpecifiedEstimateReason {
44-
override string toString() { result = "NoSpecifiedEstimateReason" }
61+
class UnspecifiedEstimateReason extends BufferWriteEstimationReason, TUnspecifiedEstimateReason {
62+
override string toString() { result = "UnspecifiedEstimateReason" }
4563

4664
override string getDescription() { result = "no reason specified" }
47-
48-
override BufferWriteEstimationReason combineWith(BufferWriteEstimationReason other) {
49-
// this reason should not be used in format specifiers, so it should not be combined
50-
// with other reasons
51-
none()
52-
}
5365
}
5466

5567
/**
@@ -60,9 +72,24 @@ class TypeBoundsAnalysis extends BufferWriteEstimationReason, TTypeBoundsAnalysi
6072
override string toString() { result = "TypeBoundsAnalysis" }
6173

6274
override string getDescription() { result = "based on type bounds" }
75+
}
6376

64-
override BufferWriteEstimationReason combineWith(BufferWriteEstimationReason other) {
65-
other != TNoSpecifiedEstimateReason() and result = TTypeBoundsAnalysis()
77+
/**
78+
* The estimation comes from non trivial bounds found via actual flow analysis,
79+
* but a widening aproximation might have been used for variables in loops.
80+
* For example
81+
* ```
82+
* for (int i = 0; i < 10; ++i) {
83+
* int j = i + i;
84+
* //... <- estimation done here based on j
85+
* }
86+
* ```
87+
*/
88+
class WidenedValueFlowAnalysis extends BufferWriteEstimationReason, TWidenedValueFlowAnalysis {
89+
override string toString() { result = "WidenedValueFlowAnalysis" }
90+
91+
override string getDescription() {
92+
result = "based on flow analysis of value bounds with a widening approximation"
6693
}
6794
}
6895

@@ -80,10 +107,6 @@ class ValueFlowAnalysis extends BufferWriteEstimationReason, TValueFlowAnalysis
80107
override string toString() { result = "ValueFlowAnalysis" }
81108

82109
override string getDescription() { result = "based on flow analysis of value bounds" }
83-
84-
override BufferWriteEstimationReason combineWith(BufferWriteEstimationReason other) {
85-
other != TNoSpecifiedEstimateReason() and result = other
86-
}
87110
}
88111

89112
class PrintfFormatAttribute extends FormatAttribute {
@@ -359,6 +382,23 @@ private int lengthInBase10(float f) {
359382
result = f.log10().floor() + 1
360383
}
361384

385+
bindingset[expr]
386+
private BufferWriteEstimationReason getEstimationReasonForIntegralExpression(Expr expr) {
387+
// we consider the range analysis non trivial if it
388+
// * constrained non-trivially both sides of a signed value, or
389+
// * constrained non-trivially the positive side of an unsigned value
390+
// expr should already be given as getFullyConverted
391+
if
392+
upperBound(expr) < exprMaxVal(expr) and
393+
(exprMinVal(expr) >= 0 or lowerBound(expr) > exprMinVal(expr))
394+
then
395+
// next we check whether the estimate may have been widened
396+
if upperBoundMayBeWidened(expr)
397+
then result = TWidenedValueFlowAnalysis()
398+
else result = TValueFlowAnalysis()
399+
else result = TTypeBoundsAnalysis()
400+
}
401+
362402
/**
363403
* A class to represent format strings that occur as arguments to invocations of formatting functions.
364404
*/
@@ -1157,12 +1197,10 @@ class FormatLiteral extends Literal {
11571197
1 + lengthInBase10(2.pow(this.getIntegralDisplayType(n).getSize() * 8 - 1)) and
11581198
// The second case uses range analysis to deduce a length that's shorter than the length
11591199
// of the number -2^31.
1160-
exists(Expr arg, float lower, float upper, float typeLower, float typeUpper |
1200+
exists(Expr arg, float lower, float upper |
11611201
arg = this.getUse().getConversionArgument(n) and
11621202
lower = lowerBound(arg.getFullyConverted()) and
1163-
upper = upperBound(arg.getFullyConverted()) and
1164-
typeLower = exprMinVal(arg.getFullyConverted()) and
1165-
typeUpper = exprMaxVal(arg.getFullyConverted())
1203+
upper = upperBound(arg.getFullyConverted())
11661204
|
11671205
valueBasedBound =
11681206
max(int cand |
@@ -1179,11 +1217,9 @@ class FormatLiteral extends Literal {
11791217
else cand = lengthInBase10(upper)
11801218
)
11811219
) and
1182-
(
1183-
if lower > typeLower or upper < typeUpper
1184-
then reason = TValueFlowAnalysis()
1185-
else reason = TTypeBoundsAnalysis()
1186-
)
1220+
// we don't want to call this on `arg.getFullyConverted()` as we want
1221+
// to detect non-trivial range analysis without taking into account up-casting
1222+
reason = getEstimationReasonForIntegralExpression(arg)
11871223
) and
11881224
len = valueBasedBound.minimum(typeBasedBound)
11891225
)
@@ -1195,12 +1231,10 @@ class FormatLiteral extends Literal {
11951231
typeBasedBound = lengthInBase10(2.pow(this.getIntegralDisplayType(n).getSize() * 8) - 1) and
11961232
// The second case uses range analysis to deduce a length that's shorter than
11971233
// the length of the number 2^31 - 1.
1198-
exists(Expr arg, float lower, float upper, float typeLower, float typeUpper |
1234+
exists(Expr arg, float lower, float upper |
11991235
arg = this.getUse().getConversionArgument(n) and
12001236
lower = lowerBound(arg.getFullyConverted()) and
1201-
upper = upperBound(arg.getFullyConverted()) and
1202-
typeLower = exprMinVal(arg.getFullyConverted()) and
1203-
typeUpper = exprMaxVal(arg.getFullyConverted())
1237+
upper = upperBound(arg.getFullyConverted())
12041238
|
12051239
valueBasedBound =
12061240
lengthInBase10(max(float cand |
@@ -1210,11 +1244,9 @@ class FormatLiteral extends Literal {
12101244
or
12111245
cand = upper
12121246
)) and
1213-
(
1214-
if lower > typeLower or upper < typeUpper
1215-
then reason = TValueFlowAnalysis()
1216-
else reason = TTypeBoundsAnalysis()
1217-
)
1247+
// we don't want to call this on `arg.getFullyConverted()` as we want
1248+
// to detect non-trivial range analysis without taking into account up-casting
1249+
reason = getEstimationReasonForIntegralExpression(arg)
12181250
) and
12191251
len = valueBasedBound.minimum(typeBasedBound)
12201252
)

cpp/ql/lib/semmle/code/cpp/security/BufferWrite.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ abstract class BufferWrite extends Expr {
7676
* can be found), specifying the reason for the estimation.
7777
*/
7878
int getMaxData(BufferWriteEstimationReason reason) {
79-
reason instanceof NoSpecifiedEstimateReason and result = getMaxData()
79+
reason instanceof UnspecifiedEstimateReason and result = getMaxData()
8080
}
8181

8282
/**

cpp/ql/src/Security/CWE/CWE-120/BadlyBoundedWrite.qhelp

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,5 @@
1818
<p>To fix the problem, either the second argument to <code>snprintf</code> should be changed to 80, or the buffer extended to 256 characters. A further improvement is to use a preprocessor define so that the size is only specified in one place, potentially preventing future recurrence of this issue.</p>
1919

2020
</example>
21-
<references>
22-
23-
24-
</references>
21+
<include src="OverrunWriteReferences.inc.qhelp" />
2522
</qhelp>
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
void sayHello()
1+
void sayHello(uint32_t userId)
22
{
3-
char buffer[10];
3+
char buffer[18];
44

5-
// BAD: this message overflows the buffer
6-
strcpy(buffer, "Hello, world!");
5+
// BAD: this message overflows the buffer if userId >= 10000
6+
sprintf(buffer, "Hello, user %d!", userId);
77

88
MessageBox(hWnd, buffer, "New Message", MB_OK);
99
}

cpp/ql/src/Security/CWE/CWE-120/OverrunWrite.qhelp

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,20 @@
66
<p>The program performs a buffer copy or write operation with no upper limit on the size of the copy, and it appears that certain inputs will cause a buffer overflow to occur in this case. In addition to causing program instability, techniques exist which may allow an attacker to use this vulnerability to execute arbitrary code.</p>
77

88
</overview>
9-
<recommendation>
10-
<p>Always control the length of buffer copy and buffer write operations. <code>strncpy</code> should be used over <code>strcpy</code>, <code>snprintf</code> over <code>sprintf</code>, and in other cases 'n-variant' functions should be preferred.</p>
11-
12-
</recommendation>
9+
<include src="OverrunWriteRecommendation.inc.qhelp" />
1310
<example>
1411
<sample src="OverrunWrite.c" />
1512

16-
<p>In this example, the call to <code>strcpy</code> copies a message of 14 characters (including the terminating null) into a buffer with space for just 10 characters. As such, the last four characters overflow the buffer resulting in undefined behavior.</p>
13+
<p>In this example, the call to <code>sprintf</code> writes a message of 14 characters (including the terminating null) plus the length of the string conversion of `userId` into a buffer with space for just 18 characters. As such, if `userId` is greater or equal to `10000`, the last characters overflow the buffer resulting in undefined behavior.</p>
1714

18-
<p>To fix this issue three changes should be made:</p>
15+
<p>To fix this issue these changes should be made:</p>
1916
<ul>
20-
<li>Control the size of the buffer using a preprocessor define.</li>
21-
<li>Replace the call to <code>strcpy</code> with <code>strncpy</code>, specifying the define as the maximum length to copy. This will prevent the buffer overflow.</li>
22-
<li>Consider increasing the buffer size, say to 20 characters, so that the message is displayed correctly.</li>
17+
<li>Control the size of the buffer by declaring it with a compile time constant.</li>
18+
<li>Preferably, replace the call to <code>sprintf</code> with <code>snprintf</code>, using the defined constant size of the buffer or `sizeof(buffer)` as maximum length to write. This will prevent the buffer overflow.</li>
19+
<li>Optionally, if `userId` is expected to be less than `10000`, then return or throw an error if `userId` is out of bounds.</li>
20+
<li>Otherwise, consider increasing the buffer size to at least 25 characters, so that the message is displayed correctly regardless of the value of `userId`.</li>
2321
</ul>
2422

2523
</example>
26-
<references>
27-
28-
<li>CERT C Coding Standard: <a href="https://www.securecoding.cert.org/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>.</li>
29-
30-
31-
<!-- LocalWords: preprocessor CWE STR
32-
-->
33-
34-
</references>
24+
<include src="OverrunWriteReferences.inc.qhelp" />
3525
</qhelp>

cpp/ql/src/Security/CWE/CWE-120/OverrunWrite.ql

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@ import semmle.code.cpp.commons.Alloc
2121
* See CWE-120/UnboundedWrite.ql for a summary of CWE-120 alert cases.
2222
*/
2323

24-
from BufferWrite bw, Expr dest, int destSize, int estimated
24+
from BufferWrite bw, Expr dest, int destSize, int estimated, BufferWriteEstimationReason reason
2525
where
2626
not bw.hasExplicitLimit() and // has no explicit size limit
2727
dest = bw.getDest() and
2828
destSize = getBufferSize(dest, _) and
29-
estimated = bw.getMaxDataLimited(_) and
29+
estimated = bw.getMaxDataLimited(reason) and
30+
// we exclude ValueFlowAnalysis as it is reported in cpp/very-likely-overruning-write
31+
not reason instanceof ValueFlowAnalysis and
3032
// we can deduce that too much data may be copied (even without
3133
// long '%f' conversions)
3234
estimated > destSize

cpp/ql/src/Security/CWE/CWE-120/OverrunWriteFloat.qhelp

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,19 @@
66
<p>The program performs a buffer copy or write operation that includes one or more float to string conversions (i.e. the %f format specifier), which may overflow the destination buffer if extreme inputs are given. In addition to causing program instability, techniques exist which may allow an attacker to use this vulnerability to execute arbitrary code.</p>
77

88
</overview>
9-
<recommendation>
10-
<p>Always control the length of buffer copy and buffer write operations. <code>strncpy</code> should be used over <code>strcpy</code>, <code>snprintf</code> over <code>sprintf</code>, and in other cases 'n-variant' functions should be preferred.</p>
11-
12-
</recommendation>
9+
<include src="OverrunWriteRecommendation.inc.qhelp" />
1310
<example>
1411
<sample src="OverrunWriteFloat.c" />
1512

16-
<p>In this example, the call to <code>sprintf</code> contains a %f format specifier. Though a 256 character buffer has been allowed, it is not sufficient for the most extreme floating point inputs. For example the representation of double value 1e304 (that is 1 with 304 zeroes after it) will overflow a buffer of this length.</p>
13+
<p>In this example, the call to <code>sprintf</code> contains a <code>%f</code> format specifier. Though a 256 character buffer has been allowed, it is not sufficient for the most extreme floating point inputs. For example the representation of double value 1e304 (that is 1 with 304 zeroes after it) will overflow a buffer of this length.</p>
1714

1815
<p>To fix this issue three changes should be made:</p>
1916
<ul>
2017
<li>Control the size of the buffer using a preprocessor define.</li>
2118
<li>Replace the call to <code>sprintf</code> with <code>snprintf</code>, specifying the define as the maximum length to copy. This will prevent the buffer overflow.</li>
22-
<li>Consider using the %g format specifier instead of %f.</li>
19+
<li>Consider using the <code>%g</code> format specifier instead of <code>%f</code>.</li>
2320
</ul>
2421

2522
</example>
26-
<references>
27-
28-
<li>CERT C Coding
29-
Standard: <a href="https://www.securecoding.cert.org/confluence/display/c/STR31-C.+Guarantee+that+storage+for+strings+has+sufficient+space+for+character+data+and+the+null+terminator">STR31-C. Guarantee
30-
that storage for strings has sufficient space for character data and
31-
the null terminator</a>.</li>
32-
33-
34-
</references>
23+
<include src="OverrunWriteReferences.inc.qhelp" />
3524
</qhelp>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<recommendation>
6+
<p>Always control the length of buffer copy and buffer write operations. <code>strncpy</code> should be used over <code>strcpy</code>, <code>snprintf</code> over <code>sprintf</code>, and in other cases 'n-variant' functions should be preferred.</p>
7+
</recommendation>
8+
</qhelp>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<references>
6+
7+
<li>CERT C Coding Standard: <a href="https://www.securecoding.cert.org/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>.</li>
8+
<li>CERT C++ Coding Standard: <a href="https://www.securecoding.cert.org/confluence/display/cplusplus/STR50-CPP.+Guarantee+that+storage+for+strings+has+sufficient+space+for+character+data+and+the+null+terminator">STR50-CPP. Guarantee that storage for strings has sufficient space for character data and the null terminator</a>.</li>
9+
10+
<!-- LocalWords: preprocessor CWE STR
11+
-->
12+
13+
</references>
14+
</qhelp>

0 commit comments

Comments
 (0)