Skip to content

Commit 6a53b7b

Browse files
authored
Merge pull request #7543 from github/rdmarsh2/cpp/hex-format-range-analysis
C++: Use range analysis for maximum lengths of `%x` formats
2 parents 2ecf0d3 + 5df6bcf commit 6a53b7b

File tree

4 files changed

+73
-11
lines changed

4 files changed

+73
-11
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* `FormatLiteral::getMaxConvertedLength` now uses range analysis to provide a
5+
more accurate length for integers formatted with `%x`

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

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,18 @@ private BufferWriteEstimationReason getEstimationReasonForIntegralExpression(Exp
399399
else result = TTypeBoundsAnalysis()
400400
}
401401

402+
/**
403+
* Gets the number of hex digits required to represent the integer represented by `f`.
404+
*
405+
* `f` is assumed to be nonnegative.
406+
*/
407+
bindingset[f]
408+
private int lengthInBase16(float f) {
409+
f = 0 and result = 1
410+
or
411+
result = (f.log2() / 4.0).floor() + 1
412+
}
413+
402414
/**
403415
* A class to represent format strings that occur as arguments to invocations of formatting functions.
404416
*/
@@ -1253,23 +1265,42 @@ class FormatLiteral extends Literal {
12531265
or
12541266
this.getConversionChar(n).toLowerCase() = "x" and
12551267
// e.g. "12345678"
1256-
exists(int sizeBytes, int baseLen |
1257-
sizeBytes =
1258-
min(int bytes |
1259-
bytes = this.getIntegralDisplayType(n).getSize()
1268+
exists(int baseLen, int typeBasedBound, int valueBasedBound |
1269+
typeBasedBound =
1270+
min(int digits |
1271+
digits = 2 * this.getIntegralDisplayType(n).getSize()
12601272
or
12611273
exists(IntegralType t |
12621274
t = this.getUse().getConversionArgument(n).getType().getUnderlyingType()
12631275
|
1264-
t.isUnsigned() and bytes = t.getSize()
1276+
t.isUnsigned() and
1277+
digits = 2 * t.getSize()
12651278
)
12661279
) and
1267-
baseLen = sizeBytes * 2 and
1268-
(
1269-
if this.hasAlternateFlag(n) then len = 2 + baseLen else len = baseLen // "0x"
1270-
)
1271-
) and
1272-
reason = TTypeBoundsAnalysis()
1280+
exists(Expr arg, float lower, float upper, float typeLower, float typeUpper |
1281+
arg = this.getUse().getConversionArgument(n) and
1282+
lower = lowerBound(arg.getFullyConverted()) and
1283+
upper = upperBound(arg.getFullyConverted()) and
1284+
typeLower = exprMinVal(arg.getFullyConverted()) and
1285+
typeUpper = exprMaxVal(arg.getFullyConverted())
1286+
|
1287+
valueBasedBound =
1288+
lengthInBase16(max(float cand |
1289+
// If lower can be negative we use `(unsigned)-1` as the candidate value.
1290+
lower < 0 and
1291+
cand = 2.pow(any(IntType t | t.isUnsigned()).getSize() * 8)
1292+
or
1293+
cand = upper
1294+
)) and
1295+
(
1296+
if lower > typeLower or upper < typeUpper
1297+
then reason = TValueFlowAnalysis()
1298+
else reason = TTypeBoundsAnalysis()
1299+
)
1300+
) and
1301+
baseLen = valueBasedBound.minimum(typeBasedBound) and
1302+
if this.hasAlternateFlag(n) then len = 2 + baseLen else len = baseLen // "0x"
1303+
)
12731304
or
12741305
this.getConversionChar(n).toLowerCase() = "p" and
12751306
exists(PointerType ptrType, int baseLen |

cpp/ql/test/query-tests/Security/CWE/CWE-120/semmle/tests/VeryLikelyOverrunWrite.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
| tests.c:120:3:120:9 | call to sprintf | This 'call to sprintf' operation requires 17 bytes but the destination is only 1 bytes. |
1515
| tests.c:121:3:121:9 | call to sprintf | This 'call to sprintf' operation requires 17 bytes but the destination is only 16 bytes. |
1616
| tests.c:136:2:136:8 | call to sprintf | This 'call to sprintf' operation requires 11 bytes but the destination is only 10 bytes. |
17+
| tests.c:186:3:186:9 | call to sprintf | This 'call to sprintf' operation requires 9 bytes but the destination is only 2 bytes. |
18+
| tests.c:189:3:189:9 | call to sprintf | This 'call to sprintf' operation requires 3 bytes but the destination is only 2 bytes. |
1719
| unions.c:26:2:26:7 | call to strcpy | This 'call to strcpy' operation requires 21 bytes but the destination is only 16 bytes. |
1820
| unions.c:27:2:27:7 | call to strcpy | This 'call to strcpy' operation requires 21 bytes but the destination is only 15 bytes. |
1921
| unions.c:27:2:27:7 | call to strcpy | This 'call to strcpy' operation requires 21 bytes but the destination is only 16 bytes. |

cpp/ql/test/query-tests/Security/CWE/CWE-120/semmle/tests/tests.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,3 +169,27 @@ void testVarSizeStruct()
169169

170170
snprintf(s->data, 10, "abcdefghijklmnopqrstuvwxyz"); // GOOD
171171
}
172+
173+
void tesHexBounds(int x) {
174+
char buffer2[2];
175+
char buffer3[3];
176+
char buffer5[5];
177+
178+
sprintf(buffer2, "%x", 1); // GOOD
179+
sprintf(buffer3, "%x", 16); // GOOD
180+
sprintf(buffer5, "%x", (unsigned short)x); // GOOD: bounded by conversion
181+
if (x < 16 && x > 0) {
182+
sprintf(buffer2, "%x", x); // GOOD: bounded by check
183+
}
184+
185+
if (x < 16) {
186+
sprintf(buffer2, "%x", x); // BAD: negative values
187+
}
188+
if (x <= 16 && x > 0) {
189+
sprintf(buffer2, "%x", x); // BAD: bound too loose
190+
}
191+
192+
if(x < 0x10000 && x > 0) {
193+
sprintf(buffer5, "%x", x); // GOOD: bounded by check
194+
}
195+
}

0 commit comments

Comments
 (0)