编者荐语:

Go 语言工具链活跃贡献者带你了解 Go 1.8 中新引入的 Address Sanitizer。

以下文章来源于Golang Contributor Club,作者Fannie Zhang


关于作者

本文作者Fannie Zhang是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信息可以访问:google/sanitizers

Go中集成Address Sanitizer

在Go中,开发者可以使用unsafe包对指针操作,引发内存访问错误。另外,Go语言可以通过CGO和C语言进行交互,内存可以在Go和C之间来回传递,这儿也会导致内存访问问题。在Go中集成Address Sanitizer能帮助检测出这些内存访问错误。

Go 1.18版支持了Address Asanitizer, 目前的实现只支持对go堆上缓冲区访问溢出的检查。

Go 1.18的版本说明:Go 1.18 Release Notes

Go 1.18的二进制和源代码下载:go1.18beta1

在Go中使用Address Sanitizer

go buildgo rungo test 等相关命令都支持 -asan 选项。当使用 -asan 选项时,工具链会自动在Go和C/C++代码中插入address sanitizer的支持。

下面以几个简单的测试程序为例,介绍 -asan 选项的用法和功能。

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):以上信息的一个总结。

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{200201203203204}
  C.test(&cIntSlice[0])
}

同样,在终端运行命令 go build -asan case2.go 编译程序, 然后运行 ./case2 程序,错误的信息被打印出:

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 选项会检查出这个内存访问错误,错误的信息如下:

更多

我们也实现了对全局缓冲区和栈上缓冲区访问错误的检查,但还未合入主分支。

原文

使用Address sanitizer自动检测Go应用中的内存错误 —— Golang Contributor Club