Skip to content

Commit aa68c51

Browse files
authored
C++: preserve Printf and BufferWrite API
1 parent 598f283 commit aa68c51

File tree

3 files changed

+106
-87
lines changed

3 files changed

+106
-87
lines changed

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

Lines changed: 93 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,22 @@ class BufferWriteEstimationReason extends TBufferWriteEstimationReason {
2020
}
2121

2222
string toString() {
23-
this = TTypeBoundsAnalysis() and result = "based on type bounds" or
23+
this = TTypeBoundsAnalysis() and result = "based on type bounds"
24+
or
2425
this = TValueFlowAnalysis() and result = "based on flow analysis of value bounds"
2526
}
2627

2728
BufferWriteEstimationReason combineWith(BufferWriteEstimationReason other) {
28-
(this = TTypeBoundsAnalysis() or other = TTypeBoundsAnalysis()) and result = TTypeBoundsAnalysis() or
29-
(this = TValueFlowAnalysis() and other = TValueFlowAnalysis()) and result = TValueFlowAnalysis()
29+
(this = TTypeBoundsAnalysis() or other = TTypeBoundsAnalysis()) and
30+
result = TTypeBoundsAnalysis()
31+
or
32+
(this = TValueFlowAnalysis() and other = TValueFlowAnalysis()) and
33+
result = TValueFlowAnalysis()
3034
}
3135
}
3236

3337
BufferWriteEstimationReason typeBoundsAnalysis() { result = TTypeBoundsAnalysis() }
38+
3439
BufferWriteEstimationReason valueFlowAnalysis() { result = TValueFlowAnalysis() }
3540

3641
class PrintfFormatAttribute extends FormatAttribute {
@@ -1014,6 +1019,8 @@ class FormatLiteral extends Literal {
10141019
* conversion specifier of this format string; has no result if this cannot
10151020
* be determined.
10161021
*/
1022+
int getMaxConvertedLength(int n) { result = max(int l | l = getMaxConvertedLength(n, _) | l) }
1023+
10171024
int getMaxConvertedLength(int n, BufferWriteEstimationReason reason) {
10181025
exists(int len |
10191026
(
@@ -1026,10 +1033,12 @@ class FormatLiteral extends Literal {
10261033
) and
10271034
(
10281035
this.getConversionChar(n) = "%" and
1029-
len = 1 and reason = TValueFlowAnalysis()
1036+
len = 1 and
1037+
reason = TValueFlowAnalysis()
10301038
or
10311039
this.getConversionChar(n).toLowerCase() = "c" and
1032-
len = 1 and reason = TValueFlowAnalysis() // e.g. 'a'
1040+
len = 1 and
1041+
reason = TValueFlowAnalysis() // e.g. 'a'
10331042
or
10341043
this.getConversionChar(n).toLowerCase() = "f" and
10351044
exists(int dot, int afterdot |
@@ -1043,7 +1052,8 @@ class FormatLiteral extends Literal {
10431052
afterdot = 6
10441053
) and
10451054
len = 1 + 309 + dot + afterdot
1046-
) and reason = TTypeBoundsAnalysis() // e.g. -1e308="-100000"...
1055+
) and
1056+
reason = TTypeBoundsAnalysis() // e.g. -1e308="-100000"...
10471057
or
10481058
this.getConversionChar(n).toLowerCase() = "e" and
10491059
exists(int dot, int afterdot |
@@ -1057,7 +1067,8 @@ class FormatLiteral extends Literal {
10571067
afterdot = 6
10581068
) and
10591069
len = 1 + 1 + dot + afterdot + 1 + 1 + 3
1060-
) and reason = TTypeBoundsAnalysis() // -1e308="-1.000000e+308"
1070+
) and
1071+
reason = TTypeBoundsAnalysis() // -1e308="-1.000000e+308"
10611072
or
10621073
this.getConversionChar(n).toLowerCase() = "g" and
10631074
exists(int dot, int afterdot |
@@ -1080,77 +1091,79 @@ class FormatLiteral extends Literal {
10801091
// (e.g. 123456, 0.000123456 are just OK)
10811092
// so case %f can be at most P characters + 4 zeroes, sign, dot = P + 6
10821093
len = (afterdot.maximum(1) + 6).maximum(1 + 1 + dot + afterdot + 1 + 1 + 3)
1083-
) and reason = TTypeBoundsAnalysis() // (e.g. "-1.59203e-319")
1094+
) and
1095+
reason = TTypeBoundsAnalysis() // (e.g. "-1.59203e-319")
10841096
or
10851097
this.getConversionChar(n).toLowerCase() = ["d", "i"] and
10861098
// e.g. -2^31 = "-2147483648"
10871099
exists(float typeBasedBound, float valueBasedBound |
1088-
// The first case handles length sub-specifiers
1089-
// Subtract one in the exponent because one bit is for the sign.
1090-
// Add 1 to account for the possible sign in the output.
1091-
typeBasedBound = 1 + lengthInBase10(2.pow(this.getIntegralDisplayType(n).getSize() * 8 - 1)) and
1092-
// The second case uses range analysis to deduce a length that's shorter than the length
1093-
// of the number -2^31.
1094-
exists(Expr arg, float lower, float upper, float typeLower, float typeUpper |
1095-
arg = this.getUse().getConversionArgument(n) and
1096-
lower = lowerBound(arg.getFullyConverted()) and
1097-
upper = upperBound(arg.getFullyConverted()) and
1098-
typeLower = exprMinVal(arg.getFullyConverted()) and
1099-
typeUpper = exprMaxVal(arg.getFullyConverted())
1100-
|
1101-
valueBasedBound =
1102-
max(int cand |
1103-
// Include the sign bit in the length if it can be negative
1104-
(
1105-
if lower < 0
1106-
then cand = 1 + lengthInBase10(lower.abs())
1107-
else cand = lengthInBase10(lower)
1108-
)
1109-
or
1110-
(
1111-
if upper < 0
1112-
then cand = 1 + lengthInBase10(upper.abs())
1113-
else cand = lengthInBase10(upper)
1114-
)
1115-
) and
1116-
(
1117-
if lower > typeLower or upper < typeUpper
1118-
then reason = TValueFlowAnalysis()
1119-
else reason = TTypeBoundsAnalysis()
1120-
)
1121-
) and
1122-
len = valueBasedBound.minimum(typeBasedBound)
1100+
// The first case handles length sub-specifiers
1101+
// Subtract one in the exponent because one bit is for the sign.
1102+
// Add 1 to account for the possible sign in the output.
1103+
typeBasedBound =
1104+
1 + lengthInBase10(2.pow(this.getIntegralDisplayType(n).getSize() * 8 - 1)) and
1105+
// The second case uses range analysis to deduce a length that's shorter than the length
1106+
// of the number -2^31.
1107+
exists(Expr arg, float lower, float upper, float typeLower, float typeUpper |
1108+
arg = this.getUse().getConversionArgument(n) and
1109+
lower = lowerBound(arg.getFullyConverted()) and
1110+
upper = upperBound(arg.getFullyConverted()) and
1111+
typeLower = exprMinVal(arg.getFullyConverted()) and
1112+
typeUpper = exprMaxVal(arg.getFullyConverted())
1113+
|
1114+
valueBasedBound =
1115+
max(int cand |
1116+
// Include the sign bit in the length if it can be negative
1117+
(
1118+
if lower < 0
1119+
then cand = 1 + lengthInBase10(lower.abs())
1120+
else cand = lengthInBase10(lower)
1121+
)
1122+
or
1123+
(
1124+
if upper < 0
1125+
then cand = 1 + lengthInBase10(upper.abs())
1126+
else cand = lengthInBase10(upper)
1127+
)
1128+
) and
1129+
(
1130+
if lower > typeLower or upper < typeUpper
1131+
then reason = TValueFlowAnalysis()
1132+
else reason = TTypeBoundsAnalysis()
1133+
)
1134+
) and
1135+
len = valueBasedBound.minimum(typeBasedBound)
11231136
)
11241137
or
11251138
this.getConversionChar(n).toLowerCase() = "u" and
11261139
// e.g. 2^32 - 1 = "4294967295"
11271140
exists(float typeBasedBound, float valueBasedBound |
1128-
// The first case handles length sub-specifiers
1129-
typeBasedBound = lengthInBase10(2.pow(this.getIntegralDisplayType(n).getSize() * 8) - 1) and
1130-
// The second case uses range analysis to deduce a length that's shorter than
1131-
// the length of the number 2^31 - 1.
1132-
exists(Expr arg, float lower, float upper, float typeLower, float typeUpper |
1133-
arg = this.getUse().getConversionArgument(n) and
1134-
lower = lowerBound(arg.getFullyConverted()) and
1135-
upper = upperBound(arg.getFullyConverted()) and
1136-
typeLower = exprMinVal(arg.getFullyConverted()) and
1137-
typeUpper = exprMaxVal(arg.getFullyConverted())
1138-
|
1139-
valueBasedBound =
1140-
lengthInBase10(max(float cand |
1141+
// The first case handles length sub-specifiers
1142+
typeBasedBound = lengthInBase10(2.pow(this.getIntegralDisplayType(n).getSize() * 8) - 1) and
1143+
// The second case uses range analysis to deduce a length that's shorter than
1144+
// the length of the number 2^31 - 1.
1145+
exists(Expr arg, float lower, float upper, float typeLower, float typeUpper |
1146+
arg = this.getUse().getConversionArgument(n) and
1147+
lower = lowerBound(arg.getFullyConverted()) and
1148+
upper = upperBound(arg.getFullyConverted()) and
1149+
typeLower = exprMinVal(arg.getFullyConverted()) and
1150+
typeUpper = exprMaxVal(arg.getFullyConverted())
1151+
|
1152+
valueBasedBound =
1153+
lengthInBase10(max(float cand |
11411154
// If lower can be negative we use `(unsigned)-1` as the candidate value.
11421155
lower < 0 and
11431156
cand = 2.pow(any(IntType t | t.isUnsigned()).getSize() * 8)
11441157
or
11451158
cand = upper
11461159
)) and
1147-
(
1148-
if lower > typeLower or upper < typeUpper
1149-
then reason = TValueFlowAnalysis()
1150-
else reason = TTypeBoundsAnalysis()
1151-
)
1152-
) and
1153-
len = valueBasedBound.minimum(typeBasedBound)
1160+
(
1161+
if lower > typeLower or upper < typeUpper
1162+
then reason = TValueFlowAnalysis()
1163+
else reason = TTypeBoundsAnalysis()
1164+
)
1165+
) and
1166+
len = valueBasedBound.minimum(typeBasedBound)
11541167
)
11551168
or
11561169
this.getConversionChar(n).toLowerCase() = "x" and
@@ -1170,7 +1183,8 @@ class FormatLiteral extends Literal {
11701183
(
11711184
if this.hasAlternateFlag(n) then len = 2 + baseLen else len = baseLen // "0x"
11721185
)
1173-
) and reason = TTypeBoundsAnalysis()
1186+
) and
1187+
reason = TTypeBoundsAnalysis()
11741188
or
11751189
this.getConversionChar(n).toLowerCase() = "p" and
11761190
exists(PointerType ptrType, int baseLen |
@@ -1179,7 +1193,8 @@ class FormatLiteral extends Literal {
11791193
(
11801194
if this.hasAlternateFlag(n) then len = 2 + baseLen else len = baseLen // "0x"
11811195
)
1182-
) and reason = TValueFlowAnalysis()
1196+
) and
1197+
reason = TValueFlowAnalysis()
11831198
or
11841199
this.getConversionChar(n).toLowerCase() = "o" and
11851200
// e.g. 2^32 - 1 = "37777777777"
@@ -1198,7 +1213,8 @@ class FormatLiteral extends Literal {
11981213
(
11991214
if this.hasAlternateFlag(n) then len = 1 + baseLen else len = baseLen // "0"
12001215
)
1201-
) and reason = TTypeBoundsAnalysis()
1216+
) and
1217+
reason = TTypeBoundsAnalysis()
12021218
or
12031219
this.getConversionChar(n).toLowerCase() = "s" and
12041220
len =
@@ -1218,6 +1234,10 @@ class FormatLiteral extends Literal {
12181234
* determining whether a buffer overflow is caused by long float to string
12191235
* conversions.
12201236
*/
1237+
int getMaxConvertedLengthLimited(int n) {
1238+
result = max(int l | l = getMaxConvertedLengthLimited(n, _) | l)
1239+
}
1240+
12211241
int getMaxConvertedLengthLimited(int n, BufferWriteEstimationReason reason) {
12221242
if this.getConversionChar(n).toLowerCase() = "f"
12231243
then result = this.getMaxConvertedLength(n, reason).minimum(8)
@@ -1269,20 +1289,20 @@ class FormatLiteral extends Literal {
12691289
result =
12701290
this.getConstantPart(n).length() + this.getMaxConvertedLength(n, headReason) +
12711291
this.getMaxConvertedLengthAfter(n + 1, tailReason) and
1272-
reason = headReason.combineWith(tailReason)
1292+
reason = headReason.combineWith(tailReason)
12731293
)
12741294
}
12751295

12761296
private int getMaxConvertedLengthAfterLimited(int n, BufferWriteEstimationReason reason) {
12771297
if n = this.getNumConvSpec()
12781298
then result = this.getConstantSuffix().length() + 1 and reason = TValueFlowAnalysis()
12791299
else
1280-
exists(BufferWriteEstimationReason headReason, BufferWriteEstimationReason tailReason |
1281-
result =
1282-
this.getConstantPart(n).length() + this.getMaxConvertedLengthLimited(n, headReason) +
1283-
this.getMaxConvertedLengthAfterLimited(n + 1, tailReason) and
1284-
reason = headReason.combineWith(tailReason)
1285-
)
1300+
exists(BufferWriteEstimationReason headReason, BufferWriteEstimationReason tailReason |
1301+
result =
1302+
this.getConstantPart(n).length() + this.getMaxConvertedLengthLimited(n, headReason) +
1303+
this.getMaxConvertedLengthAfterLimited(n + 1, tailReason) and
1304+
reason = headReason.combineWith(tailReason)
1305+
)
12861306
}
12871307

12881308
/**

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

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -69,20 +69,19 @@ abstract class BufferWrite extends Expr {
6969
* Gets an upper bound to the amount of data that's being written (if one
7070
* can be found).
7171
*/
72-
int getMaxData() { result = max(int d | d = getMaxDataWithReason(_) | d) }
72+
int getMaxData() { result = max(int d | d = getMaxData(_) | d) }
7373

7474
/**
7575
* Gets an upper bound to the amount of data that's being written (if one
7676
* can be found), except that float to string conversions are assumed to be
7777
* much smaller (8 bytes) than their true maximum length. This can be
7878
* helpful in determining the cause of a buffer overflow issue.
7979
*/
80-
int getMaxDataLimited() { result = max(int d | d = getMaxDataLimitedWithReason(_) | d) }
80+
int getMaxDataLimited() { result = max(int d | d = getMaxDataLimited(_) | d) }
8181

82+
int getMaxData(BufferWriteEstimationReason reason) { none() }
8283

83-
int getMaxDataWithReason(BufferWriteEstimationReason reason) { none() }
84-
85-
int getMaxDataLimitedWithReason(BufferWriteEstimationReason reason) { result = getMaxDataWithReason(reason) }
84+
int getMaxDataLimited(BufferWriteEstimationReason reason) { result = getMaxData(reason) }
8685

8786
/**
8887
* Gets the size of a single character of the type this
@@ -140,7 +139,7 @@ class StrCopyBW extends BufferWriteCall {
140139
result = this.getArgument(this.getParamSize()).getValue().toInt() * this.getCharSize()
141140
}
142141

143-
override int getMaxDataWithReason(BufferWriteEstimationReason reason) {
142+
override int getMaxData(BufferWriteEstimationReason reason) {
144143
// when result exists, it is an exact flow analysis
145144
reason = valueFlowAnalysis() and
146145
result =
@@ -180,7 +179,7 @@ class StrCatBW extends BufferWriteCall {
180179
result = this.getArgument(this.getParamSize()).getValue().toInt() * this.getCharSize()
181180
}
182181

183-
override int getMaxDataWithReason(BufferWriteEstimationReason reason) {
182+
override int getMaxData(BufferWriteEstimationReason reason) {
184183
// when result exists, it is an exact flow analysis
185184
reason = valueFlowAnalysis() and
186185
result =
@@ -242,14 +241,14 @@ class SprintfBW extends BufferWriteCall {
242241

243242
override Expr getDest() { result = this.getArgument(f.getOutputParameterIndex(false)) }
244243

245-
override int getMaxDataWithReason(BufferWriteEstimationReason reason) {
244+
override int getMaxData(BufferWriteEstimationReason reason) {
246245
exists(FormatLiteral fl |
247246
fl = this.(FormattingFunctionCall).getFormat() and
248247
result = fl.getMaxConvertedLengthWithReason(reason) * this.getCharSize()
249248
)
250249
}
251250

252-
override int getMaxDataLimitedWithReason(BufferWriteEstimationReason reason) {
251+
override int getMaxDataLimited(BufferWriteEstimationReason reason) {
253252
exists(FormatLiteral fl |
254253
fl = this.(FormattingFunctionCall).getFormat() and
255254
result = fl.getMaxConvertedLengthLimitedWithReason(reason) * this.getCharSize()
@@ -345,14 +344,14 @@ class SnprintfBW extends BufferWriteCall {
345344
result = this.getArgument(this.getParamSize()).getValue().toInt() * this.getCharSize()
346345
}
347346

348-
override int getMaxDataWithReason(BufferWriteEstimationReason reason) {
347+
override int getMaxData(BufferWriteEstimationReason reason) {
349348
exists(FormatLiteral fl |
350349
fl = this.(FormattingFunctionCall).getFormat() and
351350
result = fl.getMaxConvertedLengthWithReason(reason) * this.getCharSize()
352351
)
353352
}
354353

355-
override int getMaxDataLimitedWithReason(BufferWriteEstimationReason reason) {
354+
override int getMaxDataLimited(BufferWriteEstimationReason reason) {
356355
exists(FormatLiteral fl |
357356
fl = this.(FormattingFunctionCall).getFormat() and
358357
result = fl.getMaxConvertedLengthLimitedWithReason(reason) * this.getCharSize()
@@ -445,7 +444,7 @@ class ScanfBW extends BufferWrite {
445444

446445
override Expr getDest() { result = this }
447446

448-
override int getMaxDataWithReason(BufferWriteEstimationReason reason) {
447+
override int getMaxData(BufferWriteEstimationReason reason) {
449448
// when this returns, it is based on exact flow analysis
450449
reason = valueFlowAnalysis() and
451450
exists(ScanfFunctionCall fc, ScanfFormatLiteral fl, int arg |
@@ -485,7 +484,7 @@ class RealpathBW extends BufferWriteCall {
485484

486485
override Expr getASource() { result = this.getArgument(0) }
487486

488-
override int getMaxDataWithReason(BufferWriteEstimationReason reason) {
487+
override int getMaxData(BufferWriteEstimationReason reason) {
489488
// although there may be some unknown invariants guaranteeing that a real path is shorter than PATH_MAX, we can consider providing less than PATH_MAX a problem with high precision
490489
reason = valueFlowAnalysis() and
491490
result = path_max() and

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ where
2828
destSize = getBufferSize(dest, _) and
2929
// we can deduce that too much data may be copied (even without
3030
// long '%f' conversions)
31-
bw.getMaxDataLimitedWithReason(reason) > destSize
31+
bw.getMaxDataLimited(reason) > destSize
3232
select bw,
3333
"This '" + bw.getBWDesc() + "' operation requires " + bw.getMaxData() +
3434
" bytes but the destination is only " + destSize + " bytes (" + reason.toString() + ")."

0 commit comments

Comments
 (0)