Go linter in the style of go vet
to find incorrect uses of reflect.SliceHeader
and reflect.StringHeader
, and
unsafe casts between structs with architecture-sized fields.
go-safer
reports the following usage patterns:
- There is a composite literal of underlying type
reflect.SliceHeader
orreflect.StringHeader
, - There is an assignment to an instance of type
reflect.SliceHeader
orreflect.StringHeader
that was not created by casting an actual slice orstring
, and - There is a cast between struct types, where the structs contain a different number of fields with the architecture-dependently sized types
int
,uint
, oruintptr
Pattern 1 identifies code that looks like this:
func unsafeFunction(s string) []byte {
sH := (*reflect.StringHeader)(unsafe.Pointer(&s))
bH := &reflect.SliceHeader{
Data: sH.Data,
Len: sH.Len,
Cap: sH.Len,
}
return *(*[]byte)(unsafe.Pointer(bH))
}
It will also catch cases where reflect.SliceHeader
has been renamed, like in type MysteryType reflect.SliceHeader
.
Pattern 2 identifies code such as the following:
func unsafeFunction(s string) []byte {
strH := (*reflect.StringHeader)(unsafe.Pointer(&str))
sH := (*reflect.SliceHeader)(unsafe.Pointer(nil))
sH.Len = strH.Len
sH.Cap = strH.Len
sH.Data = strH.Data
return
}
safer-go
will catch the assignments to an object of type reflect.SliceHeader
. Using the control flow graph of the
function, it can see that sH
was not derived by casting a real slice (here it's nil
instead).
Pattern 3 identified casts as the following:
type A struct {
x int
}
type B struct {
y int64
}
func unsafeFunction(a A) B {
return *(*B)(unsafe.Pointer(&a))
}
There are more examples on incorrect (reported) and safe code in the test cases in the passes/*/testdata/src
directories.
If reflect.SliceHeader
or reflect.StringHeader
is not created by casting a real slice or string
, then the Go runtime
will not treat the Data
field within these types as a reference to the underlying data array. Therefore, if the garbage
collector runs just before the final cast from the literal header instance to a real slice or string
, it may collect
the original slice or string
. This can lead to an information leak vulnerability.
For more details, such as a Proof-of-Concept exploit and a suggestion for a fixed version of these unsafe patterns, read this blog post: Golang Slice Header GC-Based Data Confusion on Real-World Code
To install go-safer
, use the following command:
go get github.com/jlauinger/go-safer
This will install go-safer
to $GOPATH/bin
, so make sure that it is included in your $PATH
environment variable.
Run go-safer on a package like this:
$ go-safer example/cmd
Or supply multiple packages, separated by spaces:
$ go-safer example/cmd example/util strings
To check a package and, recursively, all its imports, use ./...
:
$ go-safer example/cmd/...
Finally, to check the package in the current directory you can use .
:
$ go-safer .
go-safer
accepts the same flags as go vet
:
Flags:
-V print version and exit
-all
no effect (deprecated)
-c int
display offending line with this many lines of context (default -1)
-cpuprofile string
write CPU profile to this file
-debug string
debug flags, any subset of "fpstv"
-fix
apply all suggested fixes
-flags
print analyzer flags in JSON
-json
emit JSON output
-memprofile string
write memory profile to this file
-source
no effect (deprecated)
-tags string
no effect (deprecated)
-trace string
write trace log to this file
-v no effect (deprecated)
Supplying the -help
flag prints the usage information for go-safer
:
$ go-safer -help
If your project uses Go modules and a go.mod
file, go-safer
will fetch all dependencies automatically before it
analyzes them. It behaves exactly like go build
would.
If you use a different form of dependency management, e.g. manual go get
, go mod vendor
or anything else, you need
to run your dependency management before running go-safer
in order to have all dependencies up to date before
analysis.
To get the source code and compile the binary, run this:
$ git clone https://github.com/jlauinger/go-safer
$ cd go-safer
$ go build
To run the test cases use the following command:
$ go test ./...
go-safer
uses the testing infrastructure from golang.org/x/tools/go/analysis/analysistest
. To add a test case, create
a new package within the bad
or good
directories in passes/sliceheader/testdata/src
. Add as many Go files to the
package as needed.
Then, register the new package in the sliceheader_test.go
file by specifying the package path.
In the test case source files, add annotation comments to the lines that should be reported (or not). The comments must look like this:
sH.Len = strH.Len // want "assigning to reflect header object" "assigning to reflect header object"
fmt.Println("hello world") // ok
Annotations that indicate a line that should be reported must begin with want
and then have the desired message twice.
For some reason, the testing infrastructure will cause go-safer
to output the annotation twice, therefore it has to be
expected twice as well to pass the test.
Test cases for the structcast
pass can be added similarly.
Since go-safer
is built upon the Go Vet standard infrastructure, you can import the passes into you own Go Vet-based
linter.
Licensed under the MIT License (the "License"). You may not use this project except in compliance with the License. You may obtain a copy of the License here.
Copyright 2020 Johannes Lauinger
This tool has been developed as part of my Master's thesis at the Software Technology Group at TU Darmstadt.