关于作者
本文作者是Go社区的贡献者之一,主要致力于Go工具链的优化和特性的支持。本文介绍的Go中Address Sanitizer的特性是由本文作者实现的。
欢迎大家留言提供对Go Address Sanitizer的建议。如果它有帮助到发现实际应用中的错误,也希望可以留言分享。这样我们会更有动力来改进Go工具链。
前言
内存访问错误,包括缓冲区溢出和释放堆内存的使用,仍然是C和C++等编程语言的严重问题。这些错误既影响了系统的稳定性,也影响了程序的安全性,因为很多恶意代码可以通过内存错误来完成入侵。另外,内存错误的排查是困难的,很多时候导致问题的地方和发生问题的地方相隔甚远。Google旗下的开源工具Address Sanitizer可以帮助我们检测此类错误。
Address Sanitizer
Address Sanitizer(ASan)是一个快速的内存错误检测工具,它可以检测以下问题:
- 访问已被释放的内存
- 堆上缓冲区访问溢出
- 栈上缓冲区访问溢出
- 全局缓冲区访问溢出
- 内存泄漏
gcc 4.8版和LLVM 3.1版及以上支持了Address Sanitizer。
详细了解Address Sanitizer信息可以访问https://github.com/google/san...。
Go中集成Address Sanitizer
在Go中,开发者可以使用unsafe包对指针操作,引发内存访问错误。另外,Go语言可以通过CGO和C语言进行交互,内存可以在Go和C之间来回传递,这儿也会导致内存访问问题。在Go中集成Address Asanitizer能帮助检测出这些内存访问错误。
Go 1.18版支持了Address Asanitizer, 目前的实现只支持对go堆上缓冲区访问溢出的检查。
Go 1.18的版本说明:https://tip.golang.org/doc/go...。
Go 1.18的二进制和源代码下载:https://go.dev/dl/#go1.18beta1。
在Go中使用Address Sanitizer
go build
,go run
和go test
等相关命令都支持-asan
选项。当使用-asan
选项时,工具链会自动在Go和C/C++代码中插入address sanitizer的支持。
下面以几个简单的测试程序为例,介绍-asan
选项的用法和功能。
1. case1.go
在这个程序中,go访问已被释放的非法C指针p
。
package main
/*
#include <stdlib.h>
#include <stdio.h>
int *p;
int* test() {
p = (int *)malloc(2 * sizeof(int));
free(p);
return p;
}
*/
import "C"
import "fmt"
func main() {
a := C.test()
*a = 2 // BOOM
fmt.Println(*a)
}
在终端运行命令go build -asan case1.go
编译程序, 然后运行./case1
程序会打印出下面的错误信息:
第一部分(ERROR): 指出错误类型use-after-free。
第二部分(WRITE):指出线程名 T0, 操作为WRITE,发生的错误的位置。
第三部分(SUMMARY):以上信息的一个总结。
2. case2.go
在这个程序中,C访问的位置超出go数组cIntSlice
的边界。
package main
/*
#include <stdlib.h>
#include <stdio.h>
int *p;
void test(int *a) {
int c = a[5]; // BOOM
printf("a[5]=%d\n", c);
}
*/
import "C"
func main() {
cIntSlice := []C.int{200, 201, 203, 203, 204}
C.test(&cIntSlice[0])
}
同样,在终端运行命令go build -asan case2.go
编译程序, 然后运行./case2
程序,错误的信息被打印出:
3. case3.go
这个程序通过使用unsafe
包修改堆上分配的数组指针,引起越界访问的错误。
package main
import (
"fmt"
"unsafe"
)
func main() {
p := new([1024*1000]int)
p[0] = 10
r := bar(&p[1024*1000-1])
fmt.Printf("r value is %d", r)
}
func bar(a *int) int {
p := (*int)(unsafe.Add(unsafe.Pointer(a), 2*unsafe.Sizeof(int(1))))
*p = 10 // BO0M
return *p
}
使用-asan
选项会检查出这个内存访问错误,错误的信息如下:
更多
我们也实现了对全局缓冲区和栈上缓冲区访问错误的检查,但还未合入主分支。