Skip to content

Commit cd40663

Browse files
committed
Address lots of review comments
1 parent 4122fd8 commit cd40663

File tree

1 file changed

+56
-18
lines changed

1 file changed

+56
-18
lines changed

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

Lines changed: 56 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -153,13 +153,13 @@ deprecated class ConversionWithoutBoundsCheckConfig extends TaintTracking::Confi
153153

154154
private int validBitSize() { result = [7, 8, 15, 16, 31, 32, 63, 64] }
155155

156-
private newtype TarchitectureBitSize =
156+
private newtype TArchitectureBitSize =
157157
TMk32Bit() or
158158
TMk64Bit() or
159159
TMkArchitectureIndependent()
160160

161161
private newtype TMaxValueState =
162-
TMkMaxValueState(int bitSize, TarchitectureBitSize architectureBitSize) {
162+
TMkMaxValueState(int bitSize, TArchitectureBitSize architectureBitSize) {
163163
bitSize = validBitSize()
164164
}
165165

@@ -168,6 +168,11 @@ private class MaxValueState extends TMaxValueState {
168168
/**
169169
* Gets the smallest bitsize where the maximum value that could get to this
170170
* point fits into an integer type whose maximum value is 2^(result) - 1.
171+
*
172+
* For example, if we know `1 << 12` can get to a particular program point,
173+
* then the result would be 15, since a 16-bit signed integer can represent
174+
* that value and that type has maximum value 2^15 -1. An unsigned 8-bit
175+
* integer cannot represent that value as its maximum value is 2^8 - 1.
171176
*/
172177
int getBitSize() { this = TMkMaxValueState(result, _) }
173178

@@ -185,7 +190,8 @@ private class MaxValueState extends TMaxValueState {
185190
* Gets the bitsize we should use for a sink.
186191
*
187192
* If the architecture bit size is known, then we should use that. Otherwise,
188-
* we should use 32 bits, because that will lead to more results.
193+
* we should use 32 bits, because that will find results that only exist on
194+
* 32-bit architectures.
189195
*/
190196
bindingset[default]
191197
int getSinkBitSize(int default) {
@@ -243,6 +249,10 @@ class UpperBoundCheckGuard extends DataFlow::RelationalComparisonNode {
243249
* Holds if the upper bound check ensures the non-constant operand is less
244250
* than or equal to the maximum value for `bitSize` and `isSigned`. In this
245251
* case, the upper bound check is a barrier guard.
252+
*
253+
* Note that we have to use floats here because integers in CodeQL are
254+
* represented by 32-bit signed integers, which cannot represent some of the
255+
* integer values which we will encounter.
246256
*/
247257
deprecated predicate isBoundFor(int bitSize, boolean isSigned) {
248258
bitSize = [8, 16, 32] and
@@ -271,7 +281,12 @@ class UpperBoundCheckGuard extends DataFlow::RelationalComparisonNode {
271281
/**
272282
* Holds if this upper bound check ensures the non-constant operand is less
273283
* than or equal to `2^(bitsize) - 1`. In this case, the upper bound check
274-
* is a barrier guard.
284+
* is a barrier guard. `architectureBitSize` is used if the constant operand
285+
* is `math.MaxInt` or `math.MaxUint`.
286+
*
287+
* Note that we have to use floats here because integers in CodeQL are
288+
* represented by 32-bit signed integers, which cannot represent some of the
289+
* integer values which we will encounter.
275290
*/
276291
predicate isBoundFor2(int bitSize, int architectureBitSize) {
277292
bitSize = validBitSize() and
@@ -311,6 +326,28 @@ class UpperBoundCheckGuard extends DataFlow::RelationalComparisonNode {
311326
* When this guarantees that a variable in the non-constant operand is less
312327
* than some value this may be a barrier guard which should block some flow
313328
* states and transform some others as they flow through.
329+
*
330+
* For example, in the following code:
331+
* ```go
332+
* if parsed <= math.MaxInt16 {
333+
* _ = uint16(parsed)
334+
* }
335+
* ```
336+
* `parsed < math.MaxInt16` is an `UpperBoundCheckGuard` and `uint16(parsed)`
337+
* is an `UpperBoundCheck` that would be a barrier for flow states with bit
338+
* size greater than 15 and would transform them to a flow state with bit size
339+
* 15 and the same architecture bit size.
340+
*
341+
* However, in the following code:
342+
* ```go
343+
* parsed, _ := strconv.ParseUint(input, 10, 32)
344+
* if parsed < 5 {
345+
* _ = uint16(parsed)
346+
* }
347+
* ```
348+
* `parsed < 5` is an `UpperBoundCheckGuard` and `uint16(parsed)` is a barrier
349+
* for all flow states and would not transform any flow states, thus
350+
* effectively blocking them.
314351
*/
315352
class UpperBoundCheck extends BarrierFlowStateTransformer {
316353
UpperBoundCheckGuard g;
@@ -320,6 +357,8 @@ class UpperBoundCheck extends BarrierFlowStateTransformer {
320357
}
321358

322359
override predicate barrierFor(MaxValueState flowstate) {
360+
// Use a default value of 32 for `MaxValueState.getSinkBitSize` because
361+
// this will find results that only exist on 32-bit architectures.
323362
g.isBoundFor2(flowstate.getBitSize(), flowstate.getSinkBitSize(32))
324363
}
325364

@@ -329,9 +368,9 @@ class UpperBoundCheck extends BarrierFlowStateTransformer {
329368
max(int bitsize |
330369
bitsize = validBitSize() and
331370
bitsize < state.getBitSize() and
371+
// Use a default value of 32 for `MaxValueState.getSinkBitSize` because
372+
// this will find results that only exist on 32-bit architectures.
332373
not g.isBoundFor2(bitsize, state.getSinkBitSize(32))
333-
|
334-
bitsize
335374
) and
336375
if exists(state.getArchitectureBitSize())
337376
then result.getArchitectureBitSize() = state.getArchitectureBitSize()
@@ -341,10 +380,10 @@ class UpperBoundCheck extends BarrierFlowStateTransformer {
341380

342381
/**
343382
* Holds if `source` is the result of a call to `strconv.Atoi`,
344-
* `strconv.ParseInt`, or `strconv.ParseUint`, `bitSize` is the bit size of
345-
* the smallest integer type which the result could be converted to without
346-
* data loss, and `isSigned` is true if the result is parsed as a signed
347-
* integer.
383+
* `strconv.ParseInt`, or `strconv.ParseUint`, `bitSize` is the `bitSize`
384+
* argument to that call (or 0 for `strconv.Atoi`) and hence must be between 0
385+
* and 64, and `isSigned` is true for `strconv.Atoi`, true for
386+
* `strconv.ParseInt` and false for `strconv.ParseUint`.
348387
*/
349388
predicate isSourceWithBitSize(DataFlow::Node source, int bitSize, boolean isSigned) {
350389
exists(DataFlow::CallNode c, IntegerParser::Range ip, int apparentBitSize |
@@ -362,9 +401,8 @@ predicate isSourceWithBitSize(DataFlow::Node source, int bitSize, boolean isSign
362401
else apparentBitSize = rawBitSize.getIntValue()
363402
)
364403
) and
365-
// Note that `getIntTypeBitSize` can return 0, so `effectiveBitSize`
366-
// can be 0. Also `effectiveBitSize` is not necessarily the bit-size
367-
// of an integer type - it can be any integer between 0 and 64.
404+
// Note that `bitSize` is not necessarily the bit-size of an integer type.
405+
// It can be any integer between 0 and 64.
368406
bitSize = replaceZeroWith(apparentBitSize, getIntTypeBitSize(source.getFile(), 0)) and
369407
isSigned = ip.isSigned()
370408
)
@@ -387,9 +425,9 @@ private module ConversionWithoutBoundsCheckConfig implements DataFlow::StateConf
387425
state.getBitSize() =
388426
min(int bitsize |
389427
bitsize = validBitSize() and
428+
// The `bitSizeForZero` argument will not be used because on this
429+
// branch `effectiveBitSize != 0`.
390430
adjustBitSize(effectiveBitSize, sourceIsSigned, 64) <= bitsize
391-
|
392-
bitsize
393431
)
394432
)
395433
)
@@ -404,6 +442,8 @@ private module ConversionWithoutBoundsCheckConfig implements DataFlow::StateConf
404442
additional predicate isSink2(DataFlow::TypeCastNode sink, FlowState state) {
405443
sink.asExpr() instanceof ConversionExpr and
406444
exists(int architectureBitSize, IntegerType integerType, int sinkBitsize, boolean sinkIsSigned |
445+
// Use a default value of 32 for `MaxValueState.getSinkBitSize` because
446+
// this will find results that only exist on 32-bit architectures.
407447
architectureBitSize = getIntTypeBitSize(sink.getFile(), state.getSinkBitSize(32)) and
408448
not (state.getArchitectureBitSize() = 32 and architectureBitSize = 64) and
409449
sink.getResultType().getUnderlyingType() = integerType and
@@ -438,9 +478,7 @@ private module ConversionWithoutBoundsCheckConfig implements DataFlow::StateConf
438478

439479
predicate isBarrier(DataFlow::Node node, FlowState state) {
440480
// Safely guarded by a barrier guard.
441-
exists(BarrierFlowStateTransformer bfst | node = bfst and bfst.barrierFor(state) |
442-
not exists(bfst.transform(state)) or bfst.transform(state) != state
443-
)
481+
exists(BarrierFlowStateTransformer bfst | node = bfst and bfst.barrierFor(state))
444482
or
445483
// When there is a flow from a source to a sink, do not allow the flow to
446484
// continue to a further sink.

0 commit comments

Comments
 (0)