-
Notifications
You must be signed in to change notification settings - Fork 76
Expand file tree
/
Copy pathPossibleMisuseOfUndetectedNaN.ql
More file actions
202 lines (183 loc) · 6.93 KB
/
PossibleMisuseOfUndetectedNaN.ql
File metadata and controls
202 lines (183 loc) · 6.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
/**
* @id c/misra/possible-misuse-of-undetected-nan
* @name DIR-4-15: Evaluation of floating-point expressions shall not lead to the undetected generation of NaNs
* @description Evaluation of floating-point expressions shall not lead to the undetected generation
* of NaNs.
* @kind path-problem
* @precision low
* @problem.severity warning
* @tags external/misra/id/dir-4-15
* correctness
* external/misra/c/2012/amendment3
* external/misra/obligation/required
*/
import cpp
import codeql.util.Boolean
import codingstandards.c.misra
import codingstandards.cpp.RestrictedRangeAnalysis
import codingstandards.cpp.FloatingPoint
import codingstandards.cpp.AlertReporting
import semmle.code.cpp.controlflow.Guards
import semmle.code.cpp.dataflow.new.DataFlow
import semmle.code.cpp.dataflow.new.TaintTracking
import semmle.code.cpp.controlflow.Dominance
abstract class PotentiallyNaNExpr extends Expr {
abstract string getReason();
}
class DomainErrorFunctionCall extends FunctionCall, PotentiallyNaNExpr {
string reason;
DomainErrorFunctionCall() { RestrictedDomainError::hasDomainError(this, reason) }
override string getReason() { result = reason }
}
// IEEE 754-1985 Section 7.1 invalid operations
class InvalidOperationExpr extends BinaryOperation, PotentiallyNaNExpr {
string reason;
InvalidOperationExpr() {
// Usual arithmetic conversions in both languages mean that if either operand is a floating
// type, the other one is converted to a floating type as well.
getAnOperand().getFullyConverted().getType() instanceof FloatingPointType and
(
// 7.1.1 propagates signaling NaNs, we rely on flow analysis and/or assume quiet NaNs, so we
// intentionally do not cover this case.
// 7.1.2: magnitude subtraction of infinities, such as +Inf + -Inf
getOperator() = "+" and
exists(Boolean sign |
exprMayEqualInfinity(getLeftOperand(), sign) and
exprMayEqualInfinity(getRightOperand(), sign.booleanNot())
) and
reason = "from addition of infinity and negative infinity"
or
// 7.1.2 continued
getOperator() = "-" and
exists(Boolean sign |
exprMayEqualInfinity(getLeftOperand(), sign) and
exprMayEqualInfinity(getRightOperand(), sign)
) and
reason = "from subtraction of an infinity from itself"
or
// 7.1.3: multiplication of zero by infinity
getOperator() = "*" and
exists(Expr zeroOp, Expr infinityOp |
zeroOp = getAnOperand() and
infinityOp = getAnOperand() and
not zeroOp = infinityOp and
exprMayEqualZero(zeroOp) and
exprMayEqualInfinity(infinityOp, _)
) and
reason = "from multiplication of zero by infinity"
or
// 7.1.4: Division of zero by zero, or infinity by infinity
getOperator() = "/" and
exprMayEqualZero(getLeftOperand()) and
exprMayEqualZero(getRightOperand()) and
reason = "from division of zero by zero"
or
// 7.1.4 continued
getOperator() = "/" and
exprMayEqualInfinity(getLeftOperand(), _) and
exprMayEqualInfinity(getRightOperand(), _) and
reason = "from division of infinity by infinity"
or
// 7.1.5: x % y where y is zero or x is infinite
getOperator() = "%" and
exprMayEqualInfinity(getLeftOperand(), _) and
reason = "from modulus of infinity"
or
// 7.1.5 continued
getOperator() = "%" and
exprMayEqualZero(getRightOperand()) and
reason = "from modulus by zero"
// 7.1.6 handles the sqrt function, not covered here.
// 7.1.7 declares exceptions during invalid conversions, which we catch as sinks in our flow
// analysis.
// 7.1.8 declares exceptions for unordered comparisons, which we catch as sinks in our flow
// analysis.
)
}
override string getReason() { result = reason }
}
module InvalidNaNUsage implements DataFlow::ConfigSig {
/**
* An expression which has non-NaN inputs and may produce a NaN output.
*/
predicate isSource(DataFlow::Node node) {
potentialSource(node) and
not exists(DataFlow::Node prior |
isAdditionalFlowStep(prior, node) and
potentialSource(prior)
)
}
/**
* An expression which may produce a NaN output.
*/
additional predicate potentialSource(DataFlow::Node node) {
node.asExpr() instanceof PotentiallyNaNExpr
}
predicate isBarrierOut(DataFlow::Node node) {
guardedNotFPClass(node.asExpr(), TNaN())
or
exists(Expr e |
e.getType() instanceof IntegralType and
e = node.asConvertedExpr()
)
}
/**
* Add an additional flow step to handle NaN propagation through floating point operations.
*/
predicate isAdditionalFlowStep(DataFlow::Node source, DataFlow::Node sink) {
exists(Operation o |
o.getAnOperand() = source.asExpr() and
o = sink.asExpr() and
o.getType() instanceof FloatingPointType
)
}
predicate isSink(DataFlow::Node node) {
not guardedNotFPClass(node.asExpr(), TNaN()) and
node instanceof InvalidNaNUsage
}
}
class InvalidNaNUsage extends DataFlow::Node {
string description;
InvalidNaNUsage() {
// Case 1: NaNs shall not be compared, except to themselves
exists(ComparisonOperation cmp |
this.asExpr() = cmp.getAnOperand() and
not hashCons(cmp.getLeftOperand()) = hashCons(cmp.getRightOperand()) and
description = "comparison, which would always evaluate to false."
)
or
// Case 2: NaNs and infinities shall not be cast to integers
exists(Conversion c |
this.asExpr() = c.getUnconverted() and
c.getExpr().getType() instanceof FloatingPointType and
c.getType() instanceof IntegralType and
description = "a cast to integer."
)
}
string getDescription() { result = description }
}
module InvalidNaNFlow = DataFlow::Global<InvalidNaNUsage>;
import InvalidNaNFlow::PathGraph
from
Element elem, InvalidNaNFlow::PathNode source, InvalidNaNFlow::PathNode sink,
InvalidNaNUsage usage, Expr sourceExpr, string sourceString, Function function,
string computedInFunction
where
not InvalidNaNFlow::PathGraph::edges(_, source, _, _) and
not InvalidNaNFlow::PathGraph::edges(sink, _, _, _) and
not sourceExpr.isFromTemplateInstantiation(_) and
not usage.asExpr().isFromTemplateInstantiation(_) and
elem = MacroUnwrapper<Expr>::unwrapElement(sink.getNode().asExpr()) and
usage = sink.getNode() and
sourceExpr = source.getNode().asExpr() and
sourceString = source.getNode().asExpr().(PotentiallyNaNExpr).getReason() and
function = sourceExpr.getEnclosingFunction() and
InvalidNaNFlow::flow(source.getNode(), usage) and
(
if not sourceExpr.getEnclosingFunction() = usage.asExpr().getEnclosingFunction()
then computedInFunction = "computed in function $@ "
else computedInFunction = ""
)
select elem, source, sink,
"Possible NaN value $@ " + computedInFunction + "flows to " + usage.getDescription(), sourceExpr,
sourceString, function, function.getName()