Skip to content
This repository was archived by the owner on Jan 5, 2023. It is now read-only.

Commit 89eae10

Browse files
committed
Address review comments 2
1 parent 4bfb2b4 commit 89eae10

File tree

3 files changed

+100
-140
lines changed

3 files changed

+100
-140
lines changed

ql/src/Security/CWE-681/IncorrectIntegerConversion.ql

Lines changed: 43 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
/**
22
* @name Incorrect conversion between integer types
3-
* @description Converting the result of strconv.Atoi, strconv.ParseInt and strconv.ParseUint
4-
* to integer types of smaller bit size can produce unexpected values.
3+
* @description Converting the result of `strconv.Atoi`, `strconv.ParseInt`,
4+
* and `strconv.ParseUint` to integer types of smaller bit size
5+
* can produce unexpected values.
56
* @kind path-problem
67
* @problem.severity warning
78
* @id go/incorrect-integer-conversion
@@ -19,7 +20,7 @@ import DataFlow::PathGraph
1920
* is true, unsigned otherwise) with `bitSize` bits.
2021
*/
2122
float getMaxIntValue(int bitSize, boolean isSigned) {
22-
bitSize in [8, 16, 32, 64] and
23+
bitSize in [8, 16, 32] and
2324
(
2425
isSigned = true and result = 2.pow(bitSize - 1) - 1
2526
or
@@ -33,15 +34,15 @@ float getMaxIntValue(int bitSize, boolean isSigned) {
3334
* architecture-dependent.
3435
*/
3536
private predicate isIncorrectIntegerConversion(int sourceBitSize, int sinkBitSize) {
36-
sourceBitSize in [0, 16, 32, 64] and
37-
sinkBitSize in [0, 8, 16, 32] and
38-
not (sourceBitSize = 0 and sinkBitSize = 0) and
39-
exists(int source, int sink |
40-
(if sourceBitSize = 0 then source = 64 else source = sourceBitSize) and
41-
if sinkBitSize = 0 then sink = 32 else sink = sinkBitSize
42-
|
43-
source > sink
44-
)
37+
sourceBitSize in [16, 32, 64] and
38+
sinkBitSize in [8, 16, 32] and
39+
sourceBitSize > sinkBitSize
40+
or
41+
sourceBitSize = 0 and
42+
sinkBitSize in [8, 16, 32]
43+
or
44+
sourceBitSize = 64 and
45+
sinkBitSize = 0
4546
}
4647

4748
/**
@@ -57,15 +58,24 @@ class ConversionWithoutBoundsCheckConfig extends TaintTracking::Configuration {
5758
ConversionWithoutBoundsCheckConfig() {
5859
sourceIsSigned in [true, false] and
5960
isIncorrectIntegerConversion(sourceBitSize, sinkBitSize) and
60-
this =
61-
sourceBitSize.toString() + sourceIsSigned.toString() + sinkBitSize.toString() +
62-
"ConversionWithoutBoundsCheckConfig"
61+
this = "ConversionWithoutBoundsCheckConfig" + sourceBitSize + sourceIsSigned + sinkBitSize
6362
}
6463

64+
int getSourceBitSize() { result = sourceBitSize }
65+
6566
override predicate isSource(DataFlow::Node source) {
66-
exists(ParserCall pc, int bitSize | source = pc.getResult(0) |
67-
(if pc.targetIsSigned() then sourceIsSigned = true else sourceIsSigned = false) and
68-
(if pc.getTargetBitSize() = 0 then bitSize = 0 else bitSize = pc.getTargetBitSize()) and
67+
exists(DataFlow::CallNode c, IntegerParser::Range ip, int bitSize |
68+
c.getTarget() = ip and source = c.getResult(0)
69+
|
70+
(
71+
if ip.getResultType(0) instanceof SignedIntegerType
72+
then sourceIsSigned = true
73+
else sourceIsSigned = false
74+
) and
75+
(
76+
bitSize = ip.getTargetBitSize() or
77+
bitSize = ip.getTargetBitSizeInput().getNode(c).getIntValue()
78+
) and
6979
// `bitSize` could be any value between 0 and 64, but we can round
7080
// it up to the nearest size of an integer type without changing
7181
// behaviour.
@@ -76,16 +86,14 @@ class ConversionWithoutBoundsCheckConfig extends TaintTracking::Configuration {
7686
/**
7787
* Holds if `sink` is a typecast to an integer type with size `bitSize` (where
7888
* 0 represents architecture-dependent) and the expression being typecast is
79-
* not also in a right-shift expression.
89+
* not also in a right-shift expression. We allow this case because it is
90+
* a common pattern to serialise `byte(v)`, `byte(v >> 8)`, and so on.
8091
*/
8192
predicate isSink(DataFlow::TypeCastNode sink, int bitSize) {
8293
exists(IntegerType integerType | sink.getType().getUnderlyingType() = integerType |
8394
bitSize = integerType.getSize()
8495
or
85-
(
86-
integerType instanceof IntType or
87-
integerType instanceof UintType
88-
) and
96+
not exists(integerType.getSize()) and
8997
bitSize = 0
9098
) and
9199
not exists(ShrExpr shrExpr |
@@ -131,12 +139,18 @@ class UpperBoundCheckGuard extends DataFlow::BarrierGuard, DataFlow::RelationalC
131139
}
132140
}
133141

142+
/** Gets a string describing the size of the integer parsed. */
143+
string describeBitSize(int bitSize) {
144+
if bitSize != 0
145+
then bitSize in [8, 16, 32, 64] and result = "a " + bitSize + "-bit integer"
146+
else result = "an integer with architecture-dependent bit size"
147+
}
148+
134149
from
135150
DataFlow::PathNode source, DataFlow::PathNode sink, ConversionWithoutBoundsCheckConfig cfg,
136-
ParserCall pc
137-
where cfg.hasFlowPath(source, sink) and pc.getResult(0) = source.getNode()
151+
DataFlow::CallNode call
152+
where cfg.hasFlowPath(source, sink) and call.getResult(0) = source.getNode()
138153
select source.getNode(), source, sink,
139-
"Incorrect conversion of " + pc.getBitSizeString() + " from " + pc.getParserName() +
140-
" to a lower bit size type " +
141-
sink.getNode().(DataFlow::TypeCastNode).getType().getUnderlyingType().getName() +
142-
" without an upper bound check."
154+
"Incorrect conversion of " + describeBitSize(cfg.getSourceBitSize()) + " from " +
155+
call.getTarget().getQualifiedName() + " to a lower bit size type " +
156+
sink.getNode().getType().getUnderlyingType().getName() + " without an upper bound check."

ql/src/semmle/go/frameworks/Stdlib.qll

Lines changed: 37 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -507,119 +507,65 @@ module Path {
507507
}
508508
}
509509

510+
/** Provides a class for modeling functions which convert strings into integers. */
511+
module IntegerParser {
512+
/**
513+
* A function that converts strings into integers.
514+
*
515+
* Extend this class to model new APIs. If you want to refine existing API models,
516+
* extend `IntegerParser` instead.
517+
*/
518+
abstract class Range extends Function {
519+
/**
520+
* Gets the maximum bit size of the return value, if this makes
521+
* sense, where 0 represents the bit size of `int` and `uint`.
522+
*/
523+
int getTargetBitSize() { none() }
524+
525+
/**
526+
* Gets the `FunctionInput` containing the maximum bit size of the
527+
* return value, if this makes sense, where 0 represents the bit
528+
* size of `int` and `uint`.
529+
*/
530+
FunctionInput getTargetBitSizeInput() { none() }
531+
}
532+
}
533+
510534
/**
511535
* Provides classes for some functions in the `strconv` package for
512536
* converting strings to numbers.
513537
*/
514538
module StrConv {
515-
/** A function that parses integers. */
516-
class Atoi extends Function {
539+
/** The `Atoi` function. */
540+
class Atoi extends IntegerParser::Range {
517541
Atoi() { this.hasQualifiedName("strconv", "Atoi") }
518-
}
519542

520-
/** A function that parses floating-point numbers. */
521-
class ParseFloat extends Function {
522-
ParseFloat() { this.hasQualifiedName("strconv", "ParseFloat") }
543+
override int getTargetBitSize() { result = 0 }
523544
}
524545

525-
/** A function that parses integers with a specifiable bit size. */
526-
class ParseInt extends Function {
546+
/** The `ParseInt` function. */
547+
class ParseInt extends IntegerParser::Range {
527548
ParseInt() { this.hasQualifiedName("strconv", "ParseInt") }
549+
550+
override FunctionInput getTargetBitSizeInput() { result.isParameter(2) }
528551
}
529552

530-
/** A function that parses unsigned integers with a specifiable bit size. */
531-
class ParseUint extends Function {
553+
/** The `ParseUint` function. */
554+
class ParseUint extends IntegerParser::Range {
532555
ParseUint() { this.hasQualifiedName("strconv", "ParseUint") }
556+
557+
override FunctionInput getTargetBitSizeInput() { result.isParameter(2) }
533558
}
534559

535560
/**
536-
* A constant that gives the size in bits of an `int` or `uint`
537-
* value on the current architecture (32 or 64).
561+
* The `IntSize` constant, that gives the size in bits of an `int` or
562+
* `uint` value on the current architecture (32 or 64).
538563
*/
539564
class IntSize extends DeclaredConstant {
540565
IntSize() { this.hasQualifiedName("strconv", "IntSize") }
541566
}
542567
}
543568

544-
/** Provides a class for modeling calls to number-parsing functions. */
545-
module ParserCall {
546-
/** A data-flow call node that parses a number. */
547-
abstract class Range extends DataFlow::CallNode {
548-
/** Gets the bit size of the type of the result number. */
549-
abstract int getTargetBitSize();
550-
551-
/** Holds if the type of the result number is signed. */
552-
abstract predicate targetIsSigned();
553-
554-
/** Gets the name of the parser function. */
555-
abstract string getParserName();
556-
}
557-
}
558-
559-
/** A call to a number-parsing function. */
560-
class ParserCall extends DataFlow::CallNode {
561-
ParserCall::Range self;
562-
563-
ParserCall() { this = self }
564-
565-
/** Gets the bit size of the type of the result number. */
566-
int getTargetBitSize() { result = self.getTargetBitSize() }
567-
568-
/** Holds if the type of the result number is signed. */
569-
predicate targetIsSigned() { self.targetIsSigned() }
570-
571-
/** Gets the name of the parser function. */
572-
string getParserName() { result = self.getParserName() }
573-
574-
/** Gets a string describing the size of the integer parsed. */
575-
string getBitSizeString() {
576-
if getTargetBitSize() != 0
577-
then result = "a " + getTargetBitSize() + "-bit integer"
578-
else result = "an integer with architecture-dependent bit-width"
579-
}
580-
}
581-
582-
/** A call to `strconv.Atoi`. */
583-
class AtoiCall extends DataFlow::CallNode, ParserCall::Range {
584-
AtoiCall() { exists(StrConv::Atoi atoi | this = atoi.getACall()) }
585-
586-
override int getTargetBitSize() { result = 0 }
587-
588-
override predicate targetIsSigned() { any() }
589-
590-
override string getParserName() { result = "strconv.Atoi" }
591-
}
592-
593-
/** A call to `strconv.ParseInt`. */
594-
class ParseIntCall extends DataFlow::CallNode, ParserCall::Range {
595-
ParseIntCall() { exists(StrConv::ParseInt parseInt | this = parseInt.getACall()) }
596-
597-
override int getTargetBitSize() {
598-
if exists(StrConv::IntSize intSize | this.getArgument(2).(DataFlow::ReadNode).reads(intSize))
599-
then result = 0
600-
else result = this.getArgument(2).getIntValue()
601-
}
602-
603-
override predicate targetIsSigned() { any() }
604-
605-
override string getParserName() { result = "strconv.ParseInt" }
606-
}
607-
608-
/** A call to `strconv.ParseUint`. */
609-
class ParseUintCall extends DataFlow::CallNode, ParserCall::Range {
610-
ParseUintCall() { exists(StrConv::ParseUint parseUint | this = parseUint.getACall()) }
611-
612-
override int getTargetBitSize() {
613-
if exists(StrConv::IntSize intSize | this.getArgument(2).(DataFlow::ReadNode).reads(intSize))
614-
then result = 0
615-
else result = this.getArgument(2).getIntValue()
616-
}
617-
618-
override predicate targetIsSigned() { none() }
619-
620-
override string getParserName() { result = "strconv.ParseUint" }
621-
}
622-
623569
/** Provides models of commonly used functions in the `strings` package. */
624570
module Strings {
625571
/** The `Join` function. */

0 commit comments

Comments
 (0)