Skip to content

Commit cf5dd85

Browse files
authored
Merge pull request #2577 from MathiasVP/multiplication-overflow-not-possible-due-to-type-width
Multiplication overflow not possible due to type width
2 parents ad0ad3a + 100ace5 commit cf5dd85

File tree

3 files changed

+150
-1
lines changed

3 files changed

+150
-1
lines changed

cpp/ql/src/Likely Bugs/Arithmetic/IntMultToLong.ql

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import cpp
2020
import semmle.code.cpp.controlflow.SSA
21+
import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis
22+
import semmle.code.cpp.rangeanalysis.RangeAnalysisUtils
2123

2224
/**
2325
* Holds if `e` is either:
@@ -64,6 +66,110 @@ int getEffectiveMulOperands(MulExpr me) {
6466
)
6567
}
6668

69+
/**
70+
* As SimpleRangeAnalysis does not support reasoning about multiplication
71+
* we create a tiny abstract interpreter for handling multiplication, which
72+
* we invoke only after weeding out of all of trivial cases that we do
73+
* not care about. By default, the maximum and minimum values are computed
74+
* using SimpleRangeAnalysis.
75+
*/
76+
class AnalyzableExpr extends Expr {
77+
float maxValue() { result = upperBound(this.getFullyConverted()) }
78+
79+
float minValue() { result = lowerBound(this.getFullyConverted()) }
80+
}
81+
82+
class ParenAnalyzableExpr extends AnalyzableExpr, ParenthesisExpr {
83+
override float maxValue() { result = this.getExpr().(AnalyzableExpr).maxValue() }
84+
85+
override float minValue() { result = this.getExpr().(AnalyzableExpr).minValue() }
86+
}
87+
88+
class MulAnalyzableExpr extends AnalyzableExpr, MulExpr {
89+
override float maxValue() {
90+
exists(float x1, float y1, float x2, float y2 |
91+
x1 = this.getLeftOperand().getFullyConverted().(AnalyzableExpr).minValue() and
92+
x2 = this.getLeftOperand().getFullyConverted().(AnalyzableExpr).maxValue() and
93+
y1 = this.getRightOperand().getFullyConverted().(AnalyzableExpr).minValue() and
94+
y2 = this.getRightOperand().getFullyConverted().(AnalyzableExpr).maxValue() and
95+
result = (x1 * y1).maximum(x1 * y2).maximum(x2 * y1).maximum(x2 * y2)
96+
)
97+
}
98+
99+
override float minValue() {
100+
exists(float x1, float x2, float y1, float y2 |
101+
x1 = this.getLeftOperand().getFullyConverted().(AnalyzableExpr).minValue() and
102+
x2 = this.getLeftOperand().getFullyConverted().(AnalyzableExpr).maxValue() and
103+
y1 = this.getRightOperand().getFullyConverted().(AnalyzableExpr).minValue() and
104+
y2 = this.getRightOperand().getFullyConverted().(AnalyzableExpr).maxValue() and
105+
result = (x1 * y1).minimum(x1 * y2).minimum(x2 * y1).minimum(x2 * y2)
106+
)
107+
}
108+
}
109+
110+
class AddAnalyzableExpr extends AnalyzableExpr, AddExpr {
111+
override float maxValue() {
112+
result = this.getLeftOperand().getFullyConverted().(AnalyzableExpr).maxValue() +
113+
this.getRightOperand().getFullyConverted().(AnalyzableExpr).maxValue()
114+
}
115+
116+
override float minValue() {
117+
result = this.getLeftOperand().getFullyConverted().(AnalyzableExpr).minValue() +
118+
this.getRightOperand().getFullyConverted().(AnalyzableExpr).minValue()
119+
}
120+
}
121+
122+
class SubAnalyzableExpr extends AnalyzableExpr, SubExpr {
123+
override float maxValue() {
124+
result = this.getLeftOperand().getFullyConverted().(AnalyzableExpr).maxValue() -
125+
this.getRightOperand().getFullyConverted().(AnalyzableExpr).minValue()
126+
}
127+
128+
override float minValue() {
129+
result = this.getLeftOperand().getFullyConverted().(AnalyzableExpr).minValue() -
130+
this.getRightOperand().getFullyConverted().(AnalyzableExpr).maxValue()
131+
}
132+
}
133+
134+
class VarAnalyzableExpr extends AnalyzableExpr, VariableAccess {
135+
VarAnalyzableExpr() { this.getTarget() instanceof StackVariable }
136+
137+
override float maxValue() {
138+
exists(SsaDefinition def, Variable v |
139+
def.getAUse(v) = this and
140+
// if there is a defining expression, use that for
141+
// computing the maximum value. Otherwise, assign the
142+
// variable the largest possible value it can hold
143+
if exists(def.getDefiningValue(v))
144+
then result = def.getDefiningValue(v).(AnalyzableExpr).maxValue()
145+
else result = upperBound(this)
146+
)
147+
}
148+
149+
override float minValue() {
150+
exists(SsaDefinition def, Variable v |
151+
def.getAUse(v) = this and
152+
if exists(def.getDefiningValue(v))
153+
then result = def.getDefiningValue(v).(AnalyzableExpr).minValue()
154+
else result = lowerBound(this)
155+
)
156+
}
157+
}
158+
159+
/**
160+
* Holds if `t` is not an instance of `IntegralType`,
161+
* or if `me` cannot be proven to not overflow
162+
*/
163+
predicate overflows(MulExpr me, Type t) {
164+
t instanceof IntegralType
165+
implies
166+
(
167+
me.(MulAnalyzableExpr).maxValue() > exprMaxVal(me)
168+
or
169+
me.(MulAnalyzableExpr).minValue() < exprMinVal(me)
170+
)
171+
}
172+
67173
from MulExpr me, Type t1, Type t2
68174
where
69175
t1 = me.getType().getUnderlyingType() and
@@ -101,7 +207,11 @@ where
101207
e = other.(BinaryOperation).getAnOperand*()
102208
) and
103209
e.(Literal).getType().getSize() = t2.getSize()
104-
)
210+
) and
211+
// only report if we cannot prove that the result of the
212+
// multiplication will be less (resp. greater) than the
213+
// maximum (resp. minimum) number we can compute.
214+
overflows(me, t1)
105215
select me,
106216
"Multiplication result may overflow '" + me.getType().toString() + "' before it is converted to '"
107217
+ me.getFullyConverted().getType().toString() + "'."

cpp/ql/test/query-tests/Likely Bugs/Arithmetic/IntMultToLong/IntMultToLong.c

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,37 @@ void use_printf(float f, double d)
9292
size_t three_chars(unsigned char a, unsigned char b, unsigned char c) {
9393
return a * b * c; // at most 16581375
9494
}
95+
96+
void g(unsigned char uchar1, unsigned char uchar2, unsigned char uchar3, int i) {
97+
unsigned long ulong1, ulong2, ulong3, ulong4, ulong5;
98+
ulong1 = (uchar1 + 1) * (uchar2 + 1); // GOOD
99+
ulong2 = (i + 1) * (uchar2 + 1); // BAD
100+
ulong3 = (uchar1 + 1) * (uchar2 + 1) * (uchar3 + 1); // GOOD
101+
102+
ulong4 = (uchar1 + (uchar1 + 1)) * (uchar2 + 1); // GOOD
103+
ulong5 = (i + (uchar1 + 1)) * (uchar2 + 1); // BAD
104+
105+
ulong5 = (uchar1 + 1073741824) * uchar2; // BAD [NOT DETECTED]
106+
ulong5 = (uchar1 + (1 << 30)) * uchar2; // BAD [NOT DETECTED]
107+
ulong5 = uchar1 * uchar1 * uchar1 * uchar2 * uchar2 * uchar2; // BAD [NOT DETECTED]
108+
ulong5 = (uchar1 + (unsigned short)(-1)) * (uchar2 + (unsigned short)(-1)); // BAD
109+
}
110+
111+
struct A {
112+
short s;
113+
int i;
114+
};
115+
116+
void g2(struct A* a, short n) {
117+
unsigned long ulong1, ulong2;
118+
ulong1 = (a->s - 1) * ((*a).s + 1); // GOOD
119+
ulong2 = a->i * (*a).i; // BAD
120+
}
121+
122+
int global_i;
123+
unsigned char global_uchar;
124+
void g3() {
125+
unsigned long ulong1, ulong2;
126+
ulong1 = global_i * global_i; // BAD
127+
ulong2 = (global_uchar + 1) * 2; // GOOD
128+
}

cpp/ql/test/query-tests/Likely Bugs/Arithmetic/IntMultToLong/IntMultToLong.expected

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,8 @@
77
| IntMultToLong.c:61:23:61:33 | ... * ... | Multiplication result may overflow 'int' before it is converted to 'long long'. |
88
| IntMultToLong.c:63:23:63:40 | ... * ... | Multiplication result may overflow 'int' before it is converted to 'long long'. |
99
| IntMultToLong.c:75:9:75:13 | ... * ... | Multiplication result may overflow 'int' before it is converted to 'size_t'. |
10+
| IntMultToLong.c:99:14:99:35 | ... * ... | Multiplication result may overflow 'int' before it is converted to 'unsigned long'. |
11+
| IntMultToLong.c:103:14:103:46 | ... * ... | Multiplication result may overflow 'int' before it is converted to 'unsigned long'. |
12+
| IntMultToLong.c:108:14:108:78 | ... * ... | Multiplication result may overflow 'int' before it is converted to 'unsigned long'. |
13+
| IntMultToLong.c:119:14:119:26 | ... * ... | Multiplication result may overflow 'int' before it is converted to 'unsigned long'. |
14+
| IntMultToLong.c:126:14:126:32 | ... * ... | Multiplication result may overflow 'int' before it is converted to 'unsigned long'. |

0 commit comments

Comments
 (0)