Skip to content

Commit 773ccfe

Browse files
committed
Refactor #1
This has one problem: we aren't able to properly set sinks to be barriers, to avoid the issue that we get flows from a source A to sink1, and also from A to sink1 and then on to sink2, and so on. These are seen in SPURIOUS results in: go/ql/test/query-tests/Security/CWE-681/IncorrectIntegerConversion.go
1 parent f536274 commit 773ccfe

9 files changed

+90
-68
lines changed

go/ql/lib/semmle/go/security/IncorrectIntegerConversionLib.qll

Lines changed: 72 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,12 @@ float getMaxIntValue(int bitSize, boolean isSigned) {
1717
* Get the size of `int` or `uint` in `file`, or 0 if it is
1818
* architecture-specific.
1919
*/
20-
int getIntTypeBitSize(File file) {
20+
bindingset[architectureSpecificBitSize]
21+
int getIntTypeBitSize(File file, int architectureSpecificBitSize) {
2122
file.constrainsIntBitSize(result)
2223
or
2324
not file.constrainsIntBitSize(_) and
24-
result = 0
25+
result = architectureSpecificBitSize
2526
}
2627

2728
/**
@@ -90,7 +91,7 @@ deprecated class ConversionWithoutBoundsCheckConfig extends TaintTracking::Confi
9091
) and
9192
(
9293
if apparentBitSize = 0
93-
then effectiveBitSize = getIntTypeBitSize(source.getFile())
94+
then effectiveBitSize = getIntTypeBitSize(source.getFile(), 0)
9495
else effectiveBitSize = apparentBitSize
9596
) and
9697
// `effectiveBitSize` could be any value between 0 and 64, but we
@@ -113,7 +114,7 @@ deprecated class ConversionWithoutBoundsCheckConfig extends TaintTracking::Confi
113114
bitSize = integerType.getSize()
114115
or
115116
not exists(integerType.getSize()) and
116-
bitSize = getIntTypeBitSize(sink.getFile())
117+
bitSize = getIntTypeBitSize(sink.getFile(), 0)
117118
) and
118119
if integerType instanceof SignedIntegerType then sinkIsSigned = true else sinkIsSigned = false
119120
) and
@@ -152,50 +153,73 @@ deprecated class ConversionWithoutBoundsCheckConfig extends TaintTracking::Confi
152153

153154
/** Flow state for ConversionWithoutBoundsCheckConfig. */
154155
newtype IntegerConversionFlowState =
155-
/** Keep track of info about the source and potential sinks. */
156-
TFlowstate(boolean sinkIsSigned, int sourceBitSize, int sinkBitSize) {
157-
sinkIsSigned in [true, false] and
158-
isIncorrectIntegerConversion(sourceBitSize, sinkBitSize)
156+
TFlowstate(int bitSize, boolean isSigned) {
157+
bitSize = [0, 8, 16, 32, 64] and
158+
isSigned = [true, false]
159159
}
160160

161-
/** Gets whether the sink is signed. */
162-
boolean getSinkIsSigned(IntegerConversionFlowState state) { state = TFlowstate(result, _, _) }
161+
/**
162+
* The integer type with bit size `bitSize` and signedness `isSigned` has
163+
* maximum value `2^result - 1`.
164+
*/
165+
bindingset[bitSize, bitSizeForZero]
166+
private int f(int bitSize, boolean isSigned, int bitSizeForZero) {
167+
exists(int effectiveBitSize | effectiveBitSize = replaceZeroWith(bitSize, bitSizeForZero) |
168+
isSigned = true and result = effectiveBitSize - 1
169+
or
170+
isSigned = false and result = effectiveBitSize
171+
)
172+
}
163173

164-
/** Gets the bit size of the source. */
165-
int getSourceBitSize(IntegerConversionFlowState state) { state = TFlowstate(_, result, _) }
174+
private module ConversionWithoutBoundsCheckConfig implements DataFlow::StateConfigSig {
175+
class FlowState extends IntegerConversionFlowState {
176+
/** Gets the signedness of the flow state. */
177+
boolean getIsSigned() { this = TFlowstate(_, result) }
166178

167-
/** Gets the bit size of the sink. */
168-
int getSinkBitSize(IntegerConversionFlowState state) { state = TFlowstate(_, _, result) }
179+
/** Gets the bit size of the flow state. */
180+
int getBitSize() { this = TFlowstate(result, _) }
169181

170-
private module ConversionWithoutBoundsCheckConfig implements DataFlow::StateConfigSig {
171-
class FlowState = IntegerConversionFlowState;
182+
string toString() { result = "FlowState(" + this.getBitSize() + "," + this.getIsSigned() + ")" }
183+
}
172184

173185
predicate isSource(DataFlow::Node source, FlowState state) {
174186
exists(
175187
DataFlow::CallNode c, IntegerParser::Range ip, int apparentBitSize, int effectiveBitSize
176188
|
177-
c.getTarget() = ip and source = c.getResult(0)
178-
|
189+
c.getTarget() = ip and
190+
source = c.getResult(0) and
179191
(
180192
apparentBitSize = ip.getTargetBitSize()
181193
or
182194
// If we are reading a variable, check if it is
183195
// `strconv.IntSize`, and use 0 if it is.
184-
exists(DataFlow::Node rawBitSize | rawBitSize = ip.getTargetBitSizeInput().getNode(c) |
196+
exists(DataFlow::Node rawBitSize |
197+
rawBitSize = ip.getTargetBitSizeInput().getNode(c) and
185198
if rawBitSize = any(Strconv::IntSize intSize).getARead()
186199
then apparentBitSize = 0
187200
else apparentBitSize = rawBitSize.getIntValue()
188201
)
189202
) and
190-
(
191-
if apparentBitSize = 0
192-
then effectiveBitSize = getIntTypeBitSize(source.getFile())
193-
else effectiveBitSize = apparentBitSize
194-
) and
195-
// `effectiveBitSize` could be any value between 0 and 64, but we
196-
// can round it up to the nearest size of an integer type without
197-
// changing behavior.
198-
getSourceBitSize(state) = min(int b | b in [0, 8, 16, 32, 64] and b >= effectiveBitSize)
203+
// Note that `getIntTypeBitSize` can return 0, so `effectiveBitSize`
204+
// can be 0. Also `effectiveBitSize` is not necessarily the bit-size
205+
// of an integer type - it can be any integer between 0 and 64.
206+
effectiveBitSize = replaceZeroWith(apparentBitSize, getIntTypeBitSize(source.getFile(), 0)) and
207+
// `state` represents an integer type for the sink which will overflow
208+
// if assigned the maximum value that can be parsed by `ip`. If both the
209+
// source and the sink are `int` or `uint` then overflow can only happen
210+
// if we are converting from `uint` to `int`.
211+
if state.getBitSize() = 0 and effectiveBitSize = 0
212+
then ip.isSigned() = false and state.getIsSigned() = true
213+
else
214+
// The maximum value for a signed type with n bits is 2^(n - 1) - 1.
215+
// The maximum value for an unsigned type with n bits is 2^n - 1. To
216+
// deal with `int` and `uint` we treat them as 64-bit if they are at
217+
// the source (so we catch that flow from `int` to `int32` is incorrect
218+
// on a 64-bit architecture) and 32-bit if they are at the sink (so we
219+
// catch that flow from `int64` to `int` is incorrect on a 32-bit
220+
// architecture), and we have already dealt with the case that both
221+
// source and sink are `int` or `uint`.
222+
f(state.getBitSize(), state.getIsSigned(), 32) < f(effectiveBitSize, ip.isSigned(), 64)
199223
)
200224
}
201225

@@ -205,18 +229,18 @@ private module ConversionWithoutBoundsCheckConfig implements DataFlow::StateConf
205229
* not also in a right-shift expression. We allow this case because it is
206230
* a common pattern to serialise `byte(v)`, `byte(v >> 8)`, and so on.
207231
*/
208-
additional predicate isSinkWithBitSize(
209-
DataFlow::TypeCastNode sink, boolean sinkIsSigned, int bitSize
210-
) {
232+
additional predicate isSink2(DataFlow::TypeCastNode sink, FlowState state) {
211233
sink.asExpr() instanceof ConversionExpr and
212234
exists(IntegerType integerType | sink.getResultType().getUnderlyingType() = integerType |
213235
(
214-
bitSize = integerType.getSize()
236+
state.getBitSize() = integerType.getSize()
215237
or
216238
not exists(integerType.getSize()) and
217-
bitSize = getIntTypeBitSize(sink.getFile())
239+
state.getBitSize() = getIntTypeBitSize(sink.getFile(), 0)
218240
) and
219-
if integerType instanceof SignedIntegerType then sinkIsSigned = true else sinkIsSigned = false
241+
if integerType instanceof SignedIntegerType
242+
then state.getIsSigned() = true
243+
else state.getIsSigned() = false
220244
) and
221245
not exists(ShrExpr shrExpr |
222246
shrExpr.getLeftOperand().getGlobalValueNumber() =
@@ -231,28 +255,20 @@ private module ConversionWithoutBoundsCheckConfig implements DataFlow::StateConf
231255
// can sanitize the result of the conversion to prevent flow on to further sinks
232256
// without needing to use `isSanitizerOut`, which doesn't work with flow states
233257
// (and therefore the legacy `TaintTracking::Configuration` class).
234-
isSinkWithBitSize(sink.getASuccessor(), getSinkIsSigned(state), getSinkBitSize(state))
258+
isSink2(sink.getASuccessor(), state)
235259
}
236260

237261
predicate isBarrier(DataFlow::Node node, FlowState state) {
238-
exists(boolean sinkIsSigned, int sinkBitSize |
239-
sinkIsSigned = getSinkIsSigned(state) and
240-
sinkBitSize = getSinkBitSize(state)
262+
// To catch flows that only happen on 32-bit architectures we consider an
263+
// architecture-dependent sink bit size to be 32.
264+
exists(UpperBoundCheckGuard g, int effectiveSinkBitSize |
265+
effectiveSinkBitSize = replaceZeroWith(state.getBitSize(), 32)
241266
|
242-
// To catch flows that only happen on 32-bit architectures we
243-
// consider an architecture-dependent sink bit size to be 32.
244-
exists(UpperBoundCheckGuard g, int effectiveSinkBitSize |
245-
if sinkBitSize != 0 then effectiveSinkBitSize = sinkBitSize else effectiveSinkBitSize = 32
246-
|
247-
node = DataFlow::BarrierGuard<upperBoundCheckGuard/3>::getABarrierNodeForGuard(g) and
248-
g.isBoundFor(effectiveSinkBitSize, sinkIsSigned)
249-
)
250-
or
251-
exists(int bitSize |
252-
isIncorrectIntegerConversion(getSourceBitSize(state), bitSize) and
253-
isSinkWithBitSize(node, sinkIsSigned, bitSize)
254-
)
267+
node = DataFlow::BarrierGuard<upperBoundCheckGuard/3>::getABarrierNodeForGuard(g) and
268+
g.isBoundFor(effectiveSinkBitSize, state.getIsSigned())
255269
)
270+
or
271+
isSink2(node, state)
256272
}
257273
}
258274

@@ -324,3 +340,8 @@ string describeBitSize(int bitSize, int intTypeBitSize) {
324340
"a number with architecture-dependent bit-width, which is constrained to be " +
325341
intTypeBitSize + "-bit by build constraints,"
326342
}
343+
344+
bindingset[inputBitSize, replacementForZero]
345+
private int replaceZeroWith(int inputBitSize, int replacementForZero) {
346+
if inputBitSize = 0 then result = replacementForZero else result = inputBitSize
347+
}

go/ql/src/Security/CWE-681/IncorrectIntegerConversionQuery.ql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,6 @@ where
2525
sinkConverted = sink.getNode().getASuccessor()
2626
select sinkConverted, source, sink,
2727
"Incorrect conversion of " +
28-
describeBitSize(getSourceBitSize(sink.getState()), getIntTypeBitSize(source.getNode().getFile()))
29-
+ " from $@ to a lower bit size type " + sinkConverted.getType().getUnderlyingType().getName() +
28+
describeBitSize(sink.getState().getBitSize(), getIntTypeBitSize(source.getNode().getFile(), 0)) +
29+
" from $@ to a lower bit size type " + sinkConverted.getType().getUnderlyingType().getName() +
3030
" without an upper bound check.", source, call.getTarget().getQualifiedName()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
failures
22
testFailures
3+
| IncorrectIntegerConversion.go:339:23:339:67 | comment | Fixed spurious result:hasValueFlow="type conversion" |

go/ql/test/query-tests/Security/CWE-681/IncorrectIntegerConversion.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ func testParseUint() {
134134
if err != nil {
135135
panic(err)
136136
}
137-
_ = int8(parsed)
137+
_ = int8(parsed) // $ hasValueFlow="type conversion"
138138
_ = uint8(parsed)
139139
_ = int16(parsed)
140140
_ = uint16(parsed)
@@ -152,7 +152,7 @@ func testParseUint() {
152152
}
153153
_ = int8(parsed) // $ hasValueFlow="type conversion"
154154
_ = uint8(parsed) // $ hasValueFlow="type conversion"
155-
_ = int16(parsed)
155+
_ = int16(parsed) // $ hasValueFlow="type conversion"
156156
_ = uint16(parsed)
157157
_ = int32(parsed)
158158
_ = uint32(parsed)
@@ -170,11 +170,11 @@ func testParseUint() {
170170
_ = uint8(parsed) // $ hasValueFlow="type conversion"
171171
_ = int16(parsed) // $ hasValueFlow="type conversion"
172172
_ = uint16(parsed) // $ hasValueFlow="type conversion"
173-
_ = int32(parsed)
173+
_ = int32(parsed) // $ hasValueFlow="type conversion"
174174
_ = uint32(parsed)
175175
_ = int64(parsed)
176176
_ = uint64(parsed)
177-
_ = int(parsed)
177+
_ = int(parsed) // $ hasValueFlow="type conversion"
178178
_ = uint(parsed)
179179
}
180180
{
@@ -188,7 +188,7 @@ func testParseUint() {
188188
_ = uint16(parsed) // $ hasValueFlow="type conversion"
189189
_ = int32(parsed) // $ hasValueFlow="type conversion"
190190
_ = uint32(parsed) // $ hasValueFlow="type conversion"
191-
_ = int64(parsed)
191+
_ = int64(parsed) // $ hasValueFlow="type conversion"
192192
_ = uint64(parsed)
193193
_ = int(parsed) // $ hasValueFlow="type conversion"
194194
_ = uint(parsed) // $ hasValueFlow="type conversion"
@@ -204,9 +204,9 @@ func testParseUint() {
204204
_ = uint16(parsed) // $ hasValueFlow="type conversion"
205205
_ = int32(parsed) // $ hasValueFlow="type conversion"
206206
_ = uint32(parsed) // $ hasValueFlow="type conversion"
207-
_ = int64(parsed)
207+
_ = int64(parsed) // $ hasValueFlow="type conversion"
208208
_ = uint64(parsed)
209-
_ = int(parsed)
209+
_ = int(parsed) // $ hasValueFlow="type conversion"
210210
_ = uint(parsed)
211211
}
212212
}
@@ -336,15 +336,15 @@ func testPathWithMoreThanOneSink(input string) {
336336
panic(err)
337337
}
338338
v1 := int16(parsed) // $ hasValueFlow="type conversion"
339-
_ = int16(v1)
339+
_ = int16(v1) // $ SPURIOUS: hasValueFlow="type conversion"
340340
}
341341
{
342342
parsed, err := strconv.ParseInt(input, 10, 32)
343343
if err != nil {
344344
panic(err)
345345
}
346346
v := int16(parsed) // $ hasValueFlow="type conversion"
347-
_ = int8(v)
347+
_ = int8(v) // $ SPURIOUS: hasValueFlow="type conversion"
348348
}
349349
{
350350
parsed, err := strconv.ParseInt(input, 10, 32)
@@ -353,7 +353,7 @@ func testPathWithMoreThanOneSink(input string) {
353353
}
354354
v1 := int32(parsed)
355355
v2 := int16(v1) // $ hasValueFlow="type conversion"
356-
_ = int8(v2)
356+
_ = int8(v2) // $ SPURIOUS: hasValueFlow="type conversion"
357357
}
358358
{
359359
parsed, err := strconv.ParseInt(input, 10, 16)

go/ql/test/query-tests/Security/CWE-681/Test32BitArchitectureBuildConstraintInFileName_386.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ func testIntSource386() {
2020
if err != nil {
2121
panic(err)
2222
}
23-
_ = int32(parsed)
23+
_ = int32(parsed) // $ hasValueFlow="type conversion"
2424
_ = uint32(parsed)
2525
}
2626
{

go/ql/test/query-tests/Security/CWE-681/Test32BitArchitectureBuildConstraints.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func testIntSource32() {
2323
if err != nil {
2424
panic(err)
2525
}
26-
_ = int32(parsed)
26+
_ = int32(parsed) // $ hasValueFlow="type conversion"
2727
_ = uint32(parsed)
2828
}
2929
{

go/ql/test/query-tests/Security/CWE-681/Test64BitArchitectureBuildConstraintInFileName_amd64.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ func testIntSinkAmd64() {
2020
if err != nil {
2121
panic(err)
2222
}
23-
_ = int(parsed)
23+
_ = int(parsed) // $ hasValueFlow="type conversion"
2424
_ = uint(parsed)
2525
}
2626
}

go/ql/test/query-tests/Security/CWE-681/Test64BitArchitectureBuildConstraints.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func testIntSink64() {
2323
if err != nil {
2424
panic(err)
2525
}
26-
_ = int(parsed)
26+
_ = int(parsed) // $ hasValueFlow="type conversion"
2727
_ = uint(parsed)
2828
}
2929
}

go/ql/test/query-tests/Security/CWE-681/TestOldBuildConstraints.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ func oldTestIntSink64() {
2424
if err != nil {
2525
panic(err)
2626
}
27-
_ = int(parsed)
27+
_ = int(parsed) // $ hasValueFlow="type conversion"
2828
_ = uint(parsed)
2929
}
3030
}

0 commit comments

Comments
 (0)