Skip to content

Commit ea2cf27

Browse files
authored
Merge pull request #16234 from owen-mc/go/incorrect-integer-conversion-type-switch-fp
Go: Fix FPs in `go/incorrect-integer-conversion` query
2 parents ac34b92 + 212a0f2 commit ea2cf27

File tree

3 files changed

+133
-32
lines changed

3 files changed

+133
-32
lines changed

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

Lines changed: 77 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -258,13 +258,14 @@ private class MaxValueState extends TMaxValueState {
258258
* A node that blocks some flow states and transforms some others as they flow
259259
* through it.
260260
*/
261-
abstract class BarrierFlowStateTransformer extends DataFlow::Node {
261+
abstract class FlowStateTransformer extends DataFlow::Node {
262262
/**
263-
* Holds if this should be a barrier for `flowstate`.
263+
* Holds if this should be a barrier for a flow state with bit size `bitSize`
264+
* and architecture bit size `architectureBitSize`.
264265
*
265266
* This includes flow states which are transformed into other flow states.
266267
*/
267-
abstract predicate barrierFor(MaxValueState flowstate);
268+
abstract predicate barrierFor(int bitSize, int architectureBitSize);
268269

269270
/**
270271
* Gets the flow state that `flowstate` is transformed into.
@@ -275,7 +276,20 @@ abstract class BarrierFlowStateTransformer extends DataFlow::Node {
275276
* transform(transform(x)) = transform(x)
276277
* ```
277278
*/
278-
abstract MaxValueState transform(MaxValueState flowstate);
279+
MaxValueState transform(MaxValueState state) {
280+
this.barrierFor(state.getBitSize(), state.getSinkBitSize()) and
281+
result.getBitSize() =
282+
max(int bitsize |
283+
bitsize = validBitSize() and
284+
bitsize < state.getBitSize() and
285+
not this.barrierFor(bitsize, state.getSinkBitSize())
286+
) and
287+
(
288+
result.getArchitectureBitSize() = state.getArchitectureBitSize()
289+
or
290+
state.architectureBitSizeUnknown() and result.architectureBitSizeUnknown()
291+
)
292+
}
279293
}
280294

281295
private predicate upperBoundCheckGuard(DataFlow::Node g, Expr e, boolean branch) {
@@ -372,31 +386,69 @@ class UpperBoundCheckGuard extends DataFlow::RelationalComparisonNode {
372386
* for all flow states and would not transform any flow states, thus
373387
* effectively blocking them.
374388
*/
375-
class UpperBoundCheck extends BarrierFlowStateTransformer {
389+
class UpperBoundCheck extends FlowStateTransformer {
376390
UpperBoundCheckGuard g;
377391

378392
UpperBoundCheck() {
379393
this = DataFlow::BarrierGuard<upperBoundCheckGuard/3>::getABarrierNodeForGuard(g)
380394
}
381395

382-
override predicate barrierFor(MaxValueState flowstate) {
383-
g.isBoundFor(flowstate.getBitSize(), flowstate.getSinkBitSize())
396+
override predicate barrierFor(int bitSize, int architectureBitSize) {
397+
g.isBoundFor(bitSize, architectureBitSize)
384398
}
399+
}
385400

386-
override MaxValueState transform(MaxValueState state) {
387-
this.barrierFor(state) and
388-
result.getBitSize() =
389-
max(int bitsize |
390-
bitsize = validBitSize() and
391-
bitsize < state.getBitSize() and
392-
not g.isBoundFor(bitsize, state.getSinkBitSize())
393-
) and
394-
(
395-
result.getArchitectureBitSize() = state.getArchitectureBitSize()
396-
or
397-
state.architectureBitSizeUnknown() and result.architectureBitSizeUnknown()
401+
private predicate integerTypeBound(IntegerType it, int bitSize, int architectureBitSize) {
402+
bitSize = validBitSize() and
403+
architectureBitSize = [32, 64] and
404+
exists(int offset | if it instanceof SignedIntegerType then offset = 1 else offset = 0 |
405+
if it instanceof IntType or it instanceof UintType
406+
then bitSize >= architectureBitSize - offset
407+
else bitSize >= it.getSize() - offset
408+
)
409+
}
410+
411+
/**
412+
* An expression which a type assertion guarantees will have a particular
413+
* integer type.
414+
*
415+
* If this is a checked type expression then this value will only be used if
416+
* the type assertion succeeded. If it is not checked then there will be a
417+
* run-time panic if the type assertion fails, so we can assume it succeeded.
418+
*/
419+
class TypeAssertionCheck extends DataFlow::ExprNode, FlowStateTransformer {
420+
IntegerType it;
421+
422+
TypeAssertionCheck() {
423+
exists(TypeAssertExpr tae |
424+
this = DataFlow::exprNode(tae.getExpr()) and
425+
it = tae.getTypeExpr().getType()
426+
)
427+
}
428+
429+
override predicate barrierFor(int bitSize, int architectureBitSize) {
430+
integerTypeBound(it, bitSize, architectureBitSize)
431+
}
432+
}
433+
434+
/**
435+
* The implicit definition of a variable with integer type for a case clause of
436+
* a type switch statement which declares a variable in its guard, which has
437+
* effectively had a checked type assertion.
438+
*/
439+
class TypeSwitchVarFlowStateTransformer extends DataFlow::SsaNode, FlowStateTransformer {
440+
IntegerType it;
441+
442+
TypeSwitchVarFlowStateTransformer() {
443+
exists(IR::TypeSwitchImplicitVariableInstruction insn, LocalVariable lv | insn.writes(lv, _) |
444+
this.getSourceVariable() = lv and
445+
it = lv.getType()
398446
)
399447
}
448+
449+
override predicate barrierFor(int bitSize, int architectureBitSize) {
450+
integerTypeBound(it, bitSize, architectureBitSize)
451+
}
400452
}
401453

402454
/**
@@ -497,7 +549,10 @@ private module ConversionWithoutBoundsCheckConfig implements DataFlow::StateConf
497549

498550
predicate isBarrier(DataFlow::Node node, FlowState state) {
499551
// Safely guarded by a barrier guard.
500-
exists(BarrierFlowStateTransformer bfst | node = bfst and bfst.barrierFor(state))
552+
exists(FlowStateTransformer fst |
553+
node = fst and
554+
fst.barrierFor(state.getBitSize(), state.getSinkBitSize())
555+
)
501556
or
502557
// When there is a flow from a source to a sink, do not allow the flow to
503558
// continue to a further sink.
@@ -507,8 +562,8 @@ private module ConversionWithoutBoundsCheckConfig implements DataFlow::StateConf
507562
predicate isAdditionalFlowStep(
508563
DataFlow::Node node1, FlowState state1, DataFlow::Node node2, FlowState state2
509564
) {
510-
// Create additional flow steps for `BarrierFlowStateTransformer`s
511-
state2 = node2.(BarrierFlowStateTransformer).transform(state1) and
565+
// Create additional flow steps for `FlowStateTransformer`s
566+
state2 = node2.(FlowStateTransformer).transform(state1) and
512567
DataFlow::simpleLocalFlowStep(node1, node2, _)
513568
}
514569
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* Added some more barriers to flow for `go/incorrect-integer-conversion` to reduce false positives, especially around type switches.

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

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -482,19 +482,61 @@ func parsePositiveInt2(value string) (int, error) {
482482
return int(i64), nil
483483
}
484484

485-
func typeAssertion(s string) {
486-
n, err := strconv.ParseInt(s, 10, 0)
487-
if err == nil {
488-
var itf interface{} = n
489-
i32 := itf.(int32)
490-
println(i32)
491-
}
492-
493-
}
494-
495485
func dealWithArchSizeCorrectly(s string) uint {
496486
if i, err := strconv.ParseUint(s, 10, 64); err == nil && i < math.MaxUint {
497487
return uint(i)
498488
}
499489
return 0
500490
}
491+
492+
func typeSwitch1(s string) {
493+
i64, _ := strconv.ParseInt(s, 10, 64)
494+
var input any = i64
495+
switch v := input.(type) {
496+
case int16, string:
497+
if _, ok := input.(string); ok {
498+
return
499+
}
500+
_ = int16(v.(int16))
501+
_ = int8(v.(int16)) // $ hasValueFlow="type assertion"
502+
case int32:
503+
_ = int32(v)
504+
_ = int8(v) // $ hasValueFlow="v"
505+
case int64:
506+
_ = int8(v) // $ hasValueFlow="v"
507+
default:
508+
_ = int8(v.(int64)) // $ hasValueFlow="type assertion"
509+
}
510+
}
511+
512+
func typeSwitch2(s string) {
513+
i64, _ := strconv.ParseInt(s, 10, 64)
514+
var input any = i64
515+
switch input.(type) {
516+
case int16, string:
517+
if _, ok := input.(string); ok {
518+
return
519+
}
520+
_ = int16(input.(int16))
521+
_ = int8(input.(int16)) // $ hasValueFlow="type assertion"
522+
case int32:
523+
_ = int32(input.(int32))
524+
_ = int8(input.(int32)) // $ hasValueFlow="type assertion"
525+
case int64:
526+
_ = int8(input.(int64)) // $ hasValueFlow="type assertion"
527+
default:
528+
_ = int8(input.(int64)) // $ hasValueFlow="type assertion"
529+
}
530+
}
531+
532+
func checkedTypeAssertion(s string) {
533+
i64, _ := strconv.ParseInt(s, 10, 64)
534+
var input any = i64
535+
if v, ok := input.(int16); ok {
536+
// Need to account for the fact that within this case clause, v is an int16
537+
_ = int16(v)
538+
_ = int8(v) // $ hasValueFlow="v"
539+
} else if v, ok := input.(int32); ok {
540+
_ = int16(v) // $ hasValueFlow="v"
541+
}
542+
}

0 commit comments

Comments
 (0)