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

Commit 69212b9

Browse files
committed
Deal with build constraints
Note that build constraints can be explicit (comments at the top of the file) or implicit (part of the file name)
1 parent 1e0b9cc commit 69212b9

9 files changed

+258
-13
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ ql/src/go.dbscheme.stats: ql/src/go.dbscheme build/stats/src.stamp extractor
107107

108108
test: all build/testdb/check-upgrade-path
109109
codeql test run ql/test --search-path .
110+
env GOARCH=386 codeql$(EXE) test run ql/test/query-tests/Security/CWE-681 --search-path .
110111
cd extractor; go test -mod=vendor ./... | grep -vF "[no test files]"
111112

112113
.PHONY: build/testdb/check-upgrade-path

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

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,19 @@ float getMaxIntValue(int bitSize, boolean isSigned) {
2828
)
2929
}
3030

31+
/**
32+
* Get the size of `int` or `uint` in `file`, or 0 if it is
33+
* architecture-specific.
34+
*/
35+
int getIntTypeBitSize(File file) {
36+
if file.hasConstrainedIntBitSize(32)
37+
then result = 32
38+
else
39+
if file.hasConstrainedIntBitSize(64)
40+
then result = 64
41+
else result = 0
42+
}
43+
3144
/**
3245
* Holds if converting from an integer types with size `sourceBitSize` to
3346
* one with size `sinkBitSize` can produce unexpected values, where 0 means
@@ -74,7 +87,9 @@ class ConversionWithoutBoundsCheckConfig extends TaintTracking::Configuration {
7487
int getSourceBitSize() { result = sourceBitSize }
7588

7689
override predicate isSource(DataFlow::Node source) {
77-
exists(DataFlow::CallNode c, IntegerParser::Range ip, int bitSize |
90+
exists(
91+
DataFlow::CallNode c, IntegerParser::Range ip, int apparentBitSize, int effectiveBitSize
92+
|
7893
c.getTarget() = ip and source = c.getResult(0)
7994
|
8095
(
@@ -83,20 +98,25 @@ class ConversionWithoutBoundsCheckConfig extends TaintTracking::Configuration {
8398
else sourceIsSigned = false
8499
) and
85100
(
86-
bitSize = ip.getTargetBitSize()
101+
apparentBitSize = ip.getTargetBitSize()
87102
or
88103
// If we are reading a variable, check if it is
89104
// `strconv.IntSize`, and use 0 if it is.
90105
exists(DataFlow::Node rawBitSize | rawBitSize = ip.getTargetBitSizeInput().getNode(c) |
91106
if rawBitSize = any(StrConv::IntSize intSize).getARead()
92-
then bitSize = 0
93-
else bitSize = rawBitSize.getIntValue()
107+
then apparentBitSize = 0
108+
else apparentBitSize = rawBitSize.getIntValue()
94109
)
95110
) and
96-
// `bitSize` could be any value between 0 and 64, but we can round
97-
// it up to the nearest size of an integer type without changing
98-
// behaviour.
99-
sourceBitSize = min(int b | b in [0, 8, 16, 32, 64] and b >= bitSize)
111+
(
112+
if apparentBitSize = 0
113+
then effectiveBitSize = getIntTypeBitSize(source.getFile())
114+
else effectiveBitSize = apparentBitSize
115+
) and
116+
// `effectiveBitSize` could be any value between 0 and 64, but we
117+
// can round it up to the nearest size of an integer type without
118+
// changing behaviour.
119+
sourceBitSize = min(int b | b in [0, 8, 16, 32, 64] and b >= effectiveBitSize)
100120
)
101121
}
102122

@@ -111,7 +131,7 @@ class ConversionWithoutBoundsCheckConfig extends TaintTracking::Configuration {
111131
bitSize = integerType.getSize()
112132
or
113133
not exists(integerType.getSize()) and
114-
bitSize = 0
134+
bitSize = getIntTypeBitSize(sink.getFile())
115135
) and
116136
not exists(ShrExpr shrExpr |
117137
shrExpr.getLeftOperand().getGlobalValueNumber() =
@@ -161,17 +181,25 @@ class UpperBoundCheckGuard extends DataFlow::BarrierGuard, DataFlow::RelationalC
161181
}
162182

163183
/** Gets a string describing the size of the integer parsed. */
164-
string describeBitSize(int bitSize) {
184+
string describeBitSize(int bitSize, int intTypeBitSize) {
185+
intTypeBitSize in [0, 32, 64] and
165186
if bitSize != 0
166187
then bitSize in [8, 16, 32, 64] and result = "a " + bitSize + "-bit integer"
167-
else result = "an integer with architecture-dependent bit size"
188+
else
189+
if intTypeBitSize = 0
190+
then result = "an integer with architecture-dependent bit size"
191+
else
192+
result =
193+
"a number with architecture-dependent bit-width, which is constrained to be " +
194+
intTypeBitSize + "-bit by build constraints,"
168195
}
169196

170197
from
171198
DataFlow::PathNode source, DataFlow::PathNode sink, ConversionWithoutBoundsCheckConfig cfg,
172199
DataFlow::CallNode call
173200
where cfg.hasFlowPath(source, sink) and call.getResult(0) = source.getNode()
174201
select source.getNode(), source, sink,
175-
"Incorrect conversion of " + describeBitSize(cfg.getSourceBitSize()) + " from " +
176-
call.getTarget().getQualifiedName() + " to a lower bit size type " +
202+
"Incorrect conversion of " +
203+
describeBitSize(cfg.getSourceBitSize(), getIntTypeBitSize(source.getNode().getFile())) +
204+
" from " + call.getTarget().getQualifiedName() + " to a lower bit size type " +
177205
sink.getNode().getType().getUnderlyingType().getName() + " without an upper bound check."

ql/src/semmle/go/Files.qll

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,57 @@ class File extends Container, @file, Documentable, ExprParent, GoModExprParent,
203203
pragma[noinline]
204204
predicate hasBuildConstraints() { exists(BuildConstraintComment bc | this = bc.getFile()) }
205205

206+
/**
207+
* Gets an architecture that is valid in a build constraint with bit
208+
* size `bitSize`.
209+
*
210+
* Information obtained from
211+
* https://github.com/golang/go/blob/98cbf45cfc6a5a50cc6ac2367f9572cb198b57c7/src/go/types/gccgosizes.go
212+
* where the first field of the struct is 4 for 32-bit architectures
213+
* and 8 for 64-bit architectures.
214+
*/
215+
private string getAnArchitecture(int bitSize) {
216+
bitSize = 32 and
217+
result in ["386", "amd64p32", "arm", "armbe", "mips", "mipsle", "mips64p32", "mips64p32le",
218+
"ppc", "s390", "sparc"]
219+
or
220+
bitSize = 64 and
221+
result in ["amd64", "arm64", "arm64be", "ppc64", "ppc64le", "mips64", "mips64le", "s390x",
222+
"sparc64"]
223+
}
224+
225+
/**
226+
* Holds if this file contains build constraints that ensure that it
227+
* is only built on architectures of bit size `bitSize`.
228+
*/
229+
predicate hasConstrainedIntBitSize(int bitSize) {
230+
hasExplicitBuildConstraintsForArchitectures(bitSize) or
231+
hasImplicitBuildConstraintForAnArchitecture(bitSize)
232+
}
233+
234+
/**
235+
* Holds if this file contains explicit build constraints that ensure
236+
* that it is only built on an architecture of bit size `bitSize`.
237+
*/
238+
predicate hasExplicitBuildConstraintsForArchitectures(int bitSize) {
239+
exists(BuildConstraintComment bcc, string bc |
240+
this = bcc.getFile() and bc = bcc.getText().splitAt("+build ", 1)
241+
|
242+
forex(string disjunct | disjunct = bc.splitAt(" ") |
243+
disjunct.splitAt(",").matches(getAnArchitecture(bitSize))
244+
)
245+
)
246+
}
247+
248+
/**
249+
* Holds if this file has a name which acts as an implicit build
250+
* constraint that ensures that it is only built on an
251+
* architecture of bit size `bitSize`.
252+
*/
253+
predicate hasImplicitBuildConstraintForAnArchitecture(int bitSize) {
254+
this.getStem().regexpMatch(".*_" + getAnArchitecture(bitSize) + "(_test)?")
255+
}
256+
206257
override string toString() { result = Container.super.toString() }
207258

208259
/** Gets the URL of this file. */

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ edges
6565
| IncorrectIntegerConversion.go:367:2:367:60 | ... := ...[0] : int64 | IncorrectIntegerConversion.go:374:6:374:19 | type conversion |
6666
| IncorrectIntegerConversion.go:367:2:367:60 | ... := ...[0] : int64 | IncorrectIntegerConversion.go:375:6:375:18 | type conversion |
6767
| IncorrectIntegerConversion.go:367:2:367:60 | ... := ...[0] : int64 | IncorrectIntegerConversion.go:376:6:376:19 | type conversion |
68+
| TestNoArchitectureBuildConstraints.go:12:3:12:48 | ... := ...[0] : int64 | TestNoArchitectureBuildConstraints.go:16:7:16:19 | type conversion |
69+
| TestNoArchitectureBuildConstraints.go:12:3:12:48 | ... := ...[0] : int64 | TestNoArchitectureBuildConstraints.go:17:7:17:20 | type conversion |
70+
| TestNoArchitectureBuildConstraints.go:20:3:20:49 | ... := ...[0] : int64 | TestNoArchitectureBuildConstraints.go:24:7:24:17 | type conversion |
71+
| TestNoArchitectureBuildConstraints.go:20:3:20:49 | ... := ...[0] : int64 | TestNoArchitectureBuildConstraints.go:25:7:25:18 | type conversion |
6872
nodes
6973
| IncorrectIntegerConversion.go:26:2:26:28 | ... := ...[0] : int | semmle.label | ... := ...[0] : int |
7074
| IncorrectIntegerConversion.go:35:41:35:50 | type conversion | semmle.label | type conversion |
@@ -168,6 +172,12 @@ nodes
168172
| IncorrectIntegerConversion.go:374:6:374:19 | type conversion | semmle.label | type conversion |
169173
| IncorrectIntegerConversion.go:375:6:375:18 | type conversion | semmle.label | type conversion |
170174
| IncorrectIntegerConversion.go:376:6:376:19 | type conversion | semmle.label | type conversion |
175+
| TestNoArchitectureBuildConstraints.go:12:3:12:48 | ... := ...[0] : int64 | semmle.label | ... := ...[0] : int64 |
176+
| TestNoArchitectureBuildConstraints.go:16:7:16:19 | type conversion | semmle.label | type conversion |
177+
| TestNoArchitectureBuildConstraints.go:17:7:17:20 | type conversion | semmle.label | type conversion |
178+
| TestNoArchitectureBuildConstraints.go:20:3:20:49 | ... := ...[0] : int64 | semmle.label | ... := ...[0] : int64 |
179+
| TestNoArchitectureBuildConstraints.go:24:7:24:17 | type conversion | semmle.label | type conversion |
180+
| TestNoArchitectureBuildConstraints.go:25:7:25:18 | type conversion | semmle.label | type conversion |
171181
#select
172182
| IncorrectIntegerConversion.go:26:2:26:28 | ... := ...[0] | IncorrectIntegerConversion.go:26:2:26:28 | ... := ...[0] : int | IncorrectIntegerConversion.go:35:41:35:50 | type conversion | Incorrect conversion of an integer with architecture-dependent bit size from strconv.Atoi to a lower bit size type int32 without an upper bound check. |
173183
| IncorrectIntegerConversion.go:65:3:65:49 | ... := ...[0] | IncorrectIntegerConversion.go:65:3:65:49 | ... := ...[0] : int64 | IncorrectIntegerConversion.go:69:7:69:18 | type conversion | Incorrect conversion of a 16-bit integer from strconv.ParseInt to a lower bit size type int8 without an upper bound check. |
@@ -231,3 +241,7 @@ nodes
231241
| IncorrectIntegerConversion.go:367:2:367:60 | ... := ...[0] | IncorrectIntegerConversion.go:367:2:367:60 | ... := ...[0] : int64 | IncorrectIntegerConversion.go:374:6:374:19 | type conversion | Incorrect conversion of an integer with architecture-dependent bit size from strconv.ParseInt to a lower bit size type uint16 without an upper bound check. |
232242
| IncorrectIntegerConversion.go:367:2:367:60 | ... := ...[0] | IncorrectIntegerConversion.go:367:2:367:60 | ... := ...[0] : int64 | IncorrectIntegerConversion.go:375:6:375:18 | type conversion | Incorrect conversion of an integer with architecture-dependent bit size from strconv.ParseInt to a lower bit size type int32 without an upper bound check. |
233243
| IncorrectIntegerConversion.go:367:2:367:60 | ... := ...[0] | IncorrectIntegerConversion.go:367:2:367:60 | ... := ...[0] : int64 | IncorrectIntegerConversion.go:376:6:376:19 | type conversion | Incorrect conversion of an integer with architecture-dependent bit size from strconv.ParseInt to a lower bit size type uint32 without an upper bound check. |
244+
| TestNoArchitectureBuildConstraints.go:12:3:12:48 | ... := ...[0] | TestNoArchitectureBuildConstraints.go:12:3:12:48 | ... := ...[0] : int64 | TestNoArchitectureBuildConstraints.go:16:7:16:19 | type conversion | Incorrect conversion of an integer with architecture-dependent bit size from strconv.ParseInt to a lower bit size type int32 without an upper bound check. |
245+
| TestNoArchitectureBuildConstraints.go:12:3:12:48 | ... := ...[0] | TestNoArchitectureBuildConstraints.go:12:3:12:48 | ... := ...[0] : int64 | TestNoArchitectureBuildConstraints.go:17:7:17:20 | type conversion | Incorrect conversion of an integer with architecture-dependent bit size from strconv.ParseInt to a lower bit size type uint32 without an upper bound check. |
246+
| TestNoArchitectureBuildConstraints.go:20:3:20:49 | ... := ...[0] | TestNoArchitectureBuildConstraints.go:20:3:20:49 | ... := ...[0] : int64 | TestNoArchitectureBuildConstraints.go:24:7:24:17 | type conversion | Incorrect conversion of a 64-bit integer from strconv.ParseInt to a lower bit size type int without an upper bound check. |
247+
| TestNoArchitectureBuildConstraints.go:20:3:20:49 | ... := ...[0] | TestNoArchitectureBuildConstraints.go:20:3:20:49 | ... := ...[0] : int64 | TestNoArchitectureBuildConstraints.go:25:7:25:18 | type conversion | Incorrect conversion of a 64-bit integer from strconv.ParseInt to a lower bit size type uint without an upper bound check. |
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Note that the filename acts as an implicit build constraint
2+
3+
package main
4+
5+
import (
6+
"strconv"
7+
)
8+
9+
func testIntSource386() {
10+
{
11+
parsed, err := strconv.ParseInt("3456", 10, 0)
12+
if err != nil {
13+
panic(err)
14+
}
15+
_ = int32(parsed) // OK
16+
_ = uint32(parsed) // OK
17+
}
18+
{
19+
parsed, err := strconv.ParseUint("3456", 10, 0)
20+
if err != nil {
21+
panic(err)
22+
}
23+
_ = int32(parsed) // OK
24+
_ = uint32(parsed) // OK
25+
}
26+
{
27+
parsed, err := strconv.Atoi("3456")
28+
if err != nil {
29+
panic(err)
30+
}
31+
_ = int32(parsed) // OK
32+
_ = uint32(parsed) // OK
33+
}
34+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// +build 386 amd64p32 arm armbe mips mipsle mips64p32 mips64p32le ppc s390 sparc
2+
// +build gc
3+
// +build go1.4
4+
5+
package main
6+
7+
import (
8+
"strconv"
9+
)
10+
11+
func testIntSource32() {
12+
{
13+
parsed, err := strconv.ParseInt("3456", 10, 0)
14+
if err != nil {
15+
panic(err)
16+
}
17+
_ = int32(parsed) // OK
18+
_ = uint32(parsed) // OK
19+
}
20+
{
21+
parsed, err := strconv.ParseUint("3456", 10, 0)
22+
if err != nil {
23+
panic(err)
24+
}
25+
_ = int32(parsed) // OK
26+
_ = uint32(parsed) // OK
27+
}
28+
{
29+
parsed, err := strconv.Atoi("3456")
30+
if err != nil {
31+
panic(err)
32+
}
33+
_ = int32(parsed) // OK
34+
_ = uint32(parsed) // OK
35+
}
36+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Note that the filename acts as an implicit build constraint
2+
3+
package main
4+
5+
import (
6+
"strconv"
7+
)
8+
9+
func testIntSinkAmd64() {
10+
{
11+
parsed, err := strconv.ParseInt("3456", 10, 64)
12+
if err != nil {
13+
panic(err)
14+
}
15+
_ = int(parsed) // OK
16+
_ = uint(parsed) // OK
17+
}
18+
{
19+
parsed, err := strconv.ParseUint("3456", 10, 64)
20+
if err != nil {
21+
panic(err)
22+
}
23+
_ = int(parsed) // OK
24+
_ = uint(parsed) // OK
25+
}
26+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// +build amd64 arm64 arm64be ppc64 ppc64le mips64 mips64le s390x sparc64
2+
// +build gc
3+
// +build go1.4
4+
5+
package main
6+
7+
import (
8+
"strconv"
9+
)
10+
11+
func testIntSink64() {
12+
{
13+
parsed, err := strconv.ParseInt("3456", 10, 64)
14+
if err != nil {
15+
panic(err)
16+
}
17+
_ = int(parsed) // OK
18+
_ = uint(parsed) // OK
19+
}
20+
{
21+
parsed, err := strconv.ParseUint("3456", 10, 64)
22+
if err != nil {
23+
panic(err)
24+
}
25+
_ = int(parsed) // OK
26+
_ = uint(parsed) // OK
27+
}
28+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// +build gc
2+
// +build go1.4
3+
4+
package main
5+
6+
import (
7+
"strconv"
8+
)
9+
10+
func testIntSizeIsArchicturallyDependent1() {
11+
{
12+
parsed, err := strconv.ParseInt("3456", 10, 0)
13+
if err != nil {
14+
panic(err)
15+
}
16+
_ = int32(parsed) // NOT OK
17+
_ = uint32(parsed) // NOT OK
18+
}
19+
{
20+
parsed, err := strconv.ParseInt("3456", 10, 64)
21+
if err != nil {
22+
panic(err)
23+
}
24+
_ = int(parsed) // NOT OK
25+
_ = uint(parsed) // NOT OK
26+
}
27+
}

0 commit comments

Comments
 (0)