Skip to content

Commit dc32806

Browse files
authored
Merge pull request github#16988 from MathiasVP/unsigned-difference-compares-eq-zero-fp-fixes
C++: Fix FPs in `cpp/unsigned-difference-expression-compared-zero`
2 parents 238c6cc + 4e916de commit dc32806

File tree

4 files changed

+151
-28
lines changed

4 files changed

+151
-28
lines changed

cpp/ql/src/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero.ql

Lines changed: 92 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis
1717
import semmle.code.cpp.rangeanalysis.RangeAnalysisUtils
1818
import semmle.code.cpp.controlflow.Guards
1919
import semmle.code.cpp.ir.dataflow.DataFlow
20+
import semmle.code.cpp.valuenumbering.GlobalValueNumbering
2021

2122
/**
2223
* Holds if `sub` is guarded by a condition which ensures that
@@ -34,45 +35,108 @@ predicate isGuarded(SubExpr sub, Expr left, Expr right) {
3435
}
3536

3637
/**
37-
* Holds if `n` is known or suspected to be less than or equal to
38-
* `sub.getLeftOperand()`.
38+
* Gets an expression that is less than or equal to `sub.getLeftOperand()`.
39+
* These serve as the base cases for `exprIsSubLeftOrLess`.
3940
*/
40-
predicate exprIsSubLeftOrLess(SubExpr sub, DataFlow::Node n) {
41+
Expr exprIsLeftOrLessBase(SubExpr sub) {
4142
interestingSubExpr(sub, _) and // Manual magic
42-
(
43-
n.asExpr() = sub.getLeftOperand()
43+
exists(Expr e | globalValueNumber(e).getAnExpr() = sub.getLeftOperand() |
44+
// sub = e - x
45+
// result = e
46+
// so:
47+
// result <= e
48+
result = e
49+
or
50+
// sub = e - x
51+
// result = e & y
52+
// so:
53+
// result = e & y <= e
54+
result.(BitwiseAndExpr).getAnOperand() = e
4455
or
45-
exists(DataFlow::Node other |
46-
// dataflow
47-
exprIsSubLeftOrLess(sub, other) and
48-
(
49-
DataFlow::localFlowStep(n, other) or
50-
DataFlow::localFlowStep(other, n)
51-
)
56+
exists(SubExpr s |
57+
// sub = e - x
58+
// result = s
59+
// s = e - y
60+
// y >= 0
61+
// so:
62+
// result = e - y <= e
63+
result = s and
64+
s.getLeftOperand() = e and
65+
lowerBound(s.getRightOperand().getFullyConverted()) >= 0
5266
)
5367
or
54-
exists(DataFlow::Node other |
55-
// guard constraining `sub`
56-
exprIsSubLeftOrLess(sub, other) and
57-
isGuarded(sub, other.asExpr(), n.asExpr()) // other >= n
68+
exists(Expr other |
69+
// sub = e - x
70+
// result = a
71+
// a = e + y
72+
// y <= 0
73+
// so:
74+
// result = e + y <= e + 0 = e
75+
result.(AddExpr).hasOperands(e, other) and
76+
upperBound(other.getFullyConverted()) <= 0
5877
)
5978
or
60-
exists(DataFlow::Node other, float p, float q |
61-
// linear access of `other`
62-
exprIsSubLeftOrLess(sub, other) and
63-
linearAccess(n.asExpr(), other.asExpr(), p, q) and // n = p * other + q
64-
p <= 1 and
65-
q <= 0
79+
exists(DivExpr d |
80+
// sub = e - x
81+
// result = d
82+
// d = e / y
83+
// y >= 1
84+
// so:
85+
// result = e / y <= e / 1 = e
86+
result = d and
87+
d.getLeftOperand() = e and
88+
lowerBound(d.getRightOperand().getFullyConverted()) >= 1
6689
)
6790
or
68-
exists(DataFlow::Node other, float p, float q |
69-
// linear access of `n`
70-
exprIsSubLeftOrLess(sub, other) and
71-
linearAccess(other.asExpr(), n.asExpr(), p, q) and // other = p * n + q
72-
p >= 1 and
73-
q >= 0
91+
exists(RShiftExpr rs |
92+
// sub = e - x
93+
// result = rs
94+
// rs = e >> y
95+
// so:
96+
// result = e >> y <= e
97+
result = rs and
98+
rs.getLeftOperand() = e
99+
)
100+
)
101+
}
102+
103+
/**
104+
* Holds if `n` is known or suspected to be less than or equal to
105+
* `sub.getLeftOperand()`.
106+
*/
107+
predicate exprIsSubLeftOrLess(SubExpr sub, DataFlow::Node n) {
108+
n.asExpr() = exprIsLeftOrLessBase(sub)
109+
or
110+
exists(DataFlow::Node other |
111+
// dataflow
112+
exprIsSubLeftOrLess(sub, other) and
113+
(
114+
DataFlow::localFlowStep(n, other) or
115+
DataFlow::localFlowStep(other, n)
74116
)
75117
)
118+
or
119+
exists(DataFlow::Node other |
120+
// guard constraining `sub`
121+
exprIsSubLeftOrLess(sub, other) and
122+
isGuarded(sub, other.asExpr(), n.asExpr()) // other >= n
123+
)
124+
or
125+
exists(DataFlow::Node other, float p, float q |
126+
// linear access of `other`
127+
exprIsSubLeftOrLess(sub, other) and
128+
linearAccess(n.asExpr(), other.asExpr(), p, q) and // n = p * other + q
129+
p <= 1 and
130+
q <= 0
131+
)
132+
or
133+
exists(DataFlow::Node other, float p, float q |
134+
// linear access of `n`
135+
exprIsSubLeftOrLess(sub, other) and
136+
linearAccess(other.asExpr(), n.asExpr(), p, q) and // other = p * n + q
137+
p >= 1 and
138+
q >= 0
139+
)
76140
}
77141

78142
predicate interestingSubExpr(SubExpr sub, RelationalOperation ro) {
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* The `cpp/unsigned-difference-expression-compared-zero` ("Unsigned difference expression compared to zero") query now produces fewer false positives.

cpp/ql/test/query-tests/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero/UnsignedDifferenceExpressionComparedZero.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@
1414
| test.cpp:276:11:276:19 | ... > ... | Unsigned subtraction can never be negative. |
1515
| test.cpp:288:10:288:18 | ... > ... | Unsigned subtraction can never be negative. |
1616
| test.cpp:312:9:312:25 | ... > ... | Unsigned subtraction can never be negative. |
17+
| test.cpp:362:8:362:16 | ... > ... | Unsigned subtraction can never be negative. |

cpp/ql/test/query-tests/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero/test.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,3 +321,57 @@ void test19() {
321321
total += get_data();
322322
}
323323
}
324+
325+
void test20(int a, bool b, unsigned long c)
326+
{
327+
int x = 0;
328+
329+
if(b) {
330+
x = (a - c) / 2;
331+
} else {
332+
x = a - c;
333+
}
334+
335+
if (a - c - x > 0) // GOOD
336+
{
337+
}
338+
}
339+
340+
uint32_t get_uint32();
341+
int64_t get_int64();
342+
343+
void test21(unsigned long a)
344+
{
345+
{
346+
int b = a & get_int64();
347+
if (a - b > 0) { } // GOOD
348+
}
349+
350+
{
351+
int b = a - get_uint32();
352+
if(a - b > 0) { } // GOOD
353+
}
354+
355+
{
356+
int64_t c = get_int64();
357+
if(c <= 0) {
358+
int64_t b = (int64_t)a + c;
359+
if(a - b > 0) { } // GOOD
360+
}
361+
int64_t b = (int64_t)a + c;
362+
if(a - b > 0) { } // BAD
363+
}
364+
365+
{
366+
unsigned c = get_uint32();
367+
if(c >= 1) {
368+
int b = a / c;
369+
if(a - b > 0) { } // GOOD
370+
}
371+
}
372+
373+
{
374+
int b = a >> get_uint32();
375+
if(a - b > 0) { } // GOOD
376+
}
377+
}

0 commit comments

Comments
 (0)