Skip to content

Commit 67fb48f

Browse files
committed
C++: use range analysis for hex format lengths
The "new" result on line 189 is a tighter bound than was previously established, not a newly introduced location.
1 parent fa9242b commit 67fb48f

File tree

2 files changed

+43
-17
lines changed

2 files changed

+43
-17
lines changed

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

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,18 @@ private int lengthInBase10(float f) {
359359
result = f.log10().floor() + 1
360360
}
361361

362+
/**
363+
* Gets the number of hex digits required to represent the integer represented by `f`.
364+
*
365+
* `f` is assumed to be nonnegative.
366+
*/
367+
bindingset[f]
368+
private int lengthInBase16(float f) {
369+
f = 0 and result = 1
370+
or
371+
result = (f.log2() / 4.0).floor() + 1
372+
}
373+
362374
/**
363375
* A class to represent format strings that occur as arguments to invocations of formatting functions.
364376
*/
@@ -1221,23 +1233,42 @@ class FormatLiteral extends Literal {
12211233
or
12221234
this.getConversionChar(n).toLowerCase() = "x" and
12231235
// e.g. "12345678"
1224-
exists(int sizeBytes, int baseLen |
1225-
sizeBytes =
1226-
min(int bytes |
1227-
bytes = this.getIntegralDisplayType(n).getSize()
1236+
exists(int baseLen, int typeBasedBound, int valueBasedBound |
1237+
typeBasedBound =
1238+
min(int digits |
1239+
digits = 2 * this.getIntegralDisplayType(n).getSize()
12281240
or
12291241
exists(IntegralType t |
12301242
t = this.getUse().getConversionArgument(n).getType().getUnderlyingType()
12311243
|
1232-
t.isUnsigned() and bytes = t.getSize()
1244+
t.isUnsigned() and
1245+
digits = 2 * t.getSize()
12331246
)
12341247
) and
1235-
baseLen = sizeBytes * 2 and
1236-
(
1237-
if this.hasAlternateFlag(n) then len = 2 + baseLen else len = baseLen // "0x"
1238-
)
1239-
) and
1240-
reason = TTypeBoundsAnalysis()
1248+
exists(Expr arg, float lower, float upper, float typeLower, float typeUpper |
1249+
arg = this.getUse().getConversionArgument(n) and
1250+
lower = lowerBound(arg.getFullyConverted()) and
1251+
upper = upperBound(arg.getFullyConverted()) and
1252+
typeLower = exprMinVal(arg.getFullyConverted()) and
1253+
typeUpper = exprMaxVal(arg.getFullyConverted())
1254+
|
1255+
valueBasedBound =
1256+
lengthInBase16(max(float cand |
1257+
// If lower can be negative we use `(unsigned)-1` as the candidate value.
1258+
lower < 0 and
1259+
cand = 2.pow(any(IntType t | t.isUnsigned()).getSize() * 8)
1260+
or
1261+
cand = upper
1262+
)) and
1263+
(
1264+
if lower > typeLower or upper < typeUpper
1265+
then reason = TValueFlowAnalysis()
1266+
else reason = TTypeBoundsAnalysis()
1267+
)
1268+
) and
1269+
baseLen = valueBasedBound.minimum(typeBasedBound) and
1270+
if this.hasAlternateFlag(n) then len = 2 + baseLen else len = baseLen // "0x"
1271+
)
12411272
or
12421273
this.getConversionChar(n).toLowerCase() = "p" and
12431274
exists(PointerType ptrType, int baseLen |

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

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +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:178:2:178:8 | call to sprintf | This 'call to sprintf' operation requires 9 bytes but the destination is only 2 bytes. |
18-
| tests.c:179:2:179:8 | call to sprintf | This 'call to sprintf' operation requires 9 bytes but the destination is only 3 bytes. |
19-
| tests.c:180:2:180:8 | call to sprintf | This 'call to sprintf' operation requires 9 bytes but the destination is only 5 bytes. |
20-
| tests.c:182:3:182:9 | call to sprintf | This 'call to sprintf' operation requires 9 bytes but the destination is only 2 bytes. |
2117
| tests.c:186:3:186:9 | call to sprintf | This 'call to sprintf' operation requires 9 bytes but the destination is only 2 bytes. |
22-
| tests.c:189:3:189:9 | call to sprintf | This 'call to sprintf' operation requires 9 bytes but the destination is only 2 bytes. |
23-
| tests.c:193:3:193:9 | call to sprintf | This 'call to sprintf' operation requires 9 bytes but the destination is only 5 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. |
2419
| unions.c:26:2:26:7 | call to strcpy | This 'call to strcpy' operation requires 21 bytes but the destination is only 16 bytes. |
2520
| unions.c:27:2:27:7 | call to strcpy | This 'call to strcpy' operation requires 21 bytes but the destination is only 15 bytes. |
2621
| unions.c:27:2:27:7 | call to strcpy | This 'call to strcpy' operation requires 21 bytes but the destination is only 16 bytes. |

0 commit comments

Comments
 (0)