20

Fannie_Zhang · 2021年12月23日

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

关于作者

本文作者是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 buildgo rungo 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程序会打印出下面的错误信息:
case1.PNG
第一部分(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程序,错误的信息被打印出:
case2.PNG

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

更多

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

推荐阅读
关注数
2441
内容数
14
介绍Arm相关的开源软件。
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息