本文介绍了如何使用OSS-fuzz对一些go项目进行模糊测试,oss-fuzz是谷歌提出的一款多引擎的模糊测试平台,该平台以docker为基础,能够实现多种语言的持续模糊测试。Google希望通过“模糊测试(fuzz testing,fuzzing)”为程序提供随机数据输入,作为开源开发的标准部分,oss-fuzz能够针对开源软件进行持续的模糊测试,其测试开发团队也提到“OSS-Fuzz的目的是利用更新的模糊测试技术与可拓展的分布式执行相结合,提高一般软件基础架构的安全性与稳定性。OSS-Fuzz结合了多种模糊测试技术/漏洞捕捉技术(即原来的libfuzzer)与清洗技术(即原来的AddressSanitizer),并且通过ClusterFuzz为大规模可分布式执行提供了测试环境,当然fuzzer也可以选择AFL,libfuzzer。在oss-fuzz中,go语言由于其运行环境,如网络问题的联通性等问题,相比于C与C++编写的项目,环境搭建较为复杂,本文以容器运行时项目containerd为例,演示如何使用oss-fuzz构建模糊测试句号并介绍了go语言模糊测试中的一个开源项目 go-fuzz-header。
项目地址 :https://github.com/google/oss-fuzz
先将oss-fuzz下载到本地,project文件夹下列出了集成到oss-fuzz中进行持续模糊测试的项目,目前有将近600个项目,如下图所示
其中每个项目下有3个主要的文件,分别为project.yaml
,Dockerfile
和build.sh
。
该文件记录了项目的基本信息,以containerd
为例,该文件夹下的project.yaml
如下:
Dockerfile
为项目定义了docker 镜像,build.sh
也将在镜像中运行 ,containerd
的Dockerfile
如下,
在Dockerfile中可以看到cncf-fuzzing
的项目,该项目致力于将CNCF中的开源项目集成到OSS-fuzz中进行持续的模糊测试,如kubernetes、cri-o、runc等。
构建脚本,用来编译项目的,生成的二进制文件应放在$OUT
中,以示例,一般就是编译和复制语句,示例如下
以下位置对应的环境变量
$OUT
->/out:用来存储构建好的文件
$SRC
-> /src: 放源文件的位置
$WORK
-> work: 存储中间文件的位置
更多的变量可以参考官方文档
1.确保Docker安装成功以及主机能够访问外网。 2.克隆 oss-fuzz项目代码到本地。 3.设置Dockerd走代理以解决pull镜像时无法访问的问题。 oss中用到的镜像都需要从谷歌拉取,有两种解决方法,给docker挂个代理然后直接pull ; 或者利用github+dockerhub的方法将所有的镜像先拖到dockerhub上,然后将源码中所有的gcr.io/oss-fuzz-base/xxxx
改成对应dockerhub上的就行 ;这里使用直接给docker走代理的方式,
首先创建 /etc/systemd/system/docker.service.d/proxy.conf
配置文件,添加以下内容设置代理:
然后重新加载配置并重启服务:
检查加载的配置是否生效
4.修改containerd
文件下的Dockerfile
如下,首先是加了ENV
配置环境变量设置容器内部的网络代理,确定容器内部能够在git clone
或者 go install
等命令时不会报错,这里注意地址要填主机的ip,不能是127.0.0.1;其次修改containerd
的分支为1.6
版本,因为最新版本的containerd 的go mod 规定的是go语言的1.18版本, 目前的go环境基础容器暂时不支持 go 1.18
。
1.构建fuzz的基本镜像
第一次构建需要下载很多基础镜像,如果在pull 镜像时出现gcr.io
的网络连接问题,则需要检查代理是否生效。
成功构建镜像后,查看镜像列表,应该如下图所示,包括oss-fuzz提供的几个基础环境的镜像,和红框内的构建的containerd
的镜像。
2.构建fuzz目标
构建完成后,在/path/to/oss-fuzz/build/out/containerd
文件夹会生成对应编译好的harness二进制文件,如 fuzz_apply
、fuzz_archive_export
、fuzz_parse_auth
等,每一个harness
对应的一个fuzz目标。
harness的构建源码可以从containerd 项目中找到,每一个fuzz函数都对应一个harness。下图为containerd中的构建脚本,其中 compile_go_fuzzer
对应的编译引擎为go-fuzz
,在containerd
中使用的是在go-fuzz基础上改进的go-fuzz-header。compile_native_go_fuzzer 对应的是 go 1.18
中的原生模糊测试。 在CNCF的很多go语言项目的模糊测试中,都用到了go-fuzz-header
,前面的文章中已经介绍了go-fuzz 和go native fuzz ,相比于go-fuzz,go原生模糊测试引擎除了标准字节数组外,还可以为 Harness 提供如int,bool等多种类型 , 但一些项目可能需要更复杂的类型,如结构、映射和切片 ,go-fuzz-header就是为了解决对复杂类型的结构体进行模糊测试的挑战而出现的。以 containerd
中的 FuzzParseAuth 为例:
go-fuzz-header
首先使用模糊引擎提供的随机字节 data 创建一个新的Consumer , 之后f
调用GenerateStruct
方法根据模糊测试引擎提供的随机数据来填充auth
结构体进行测试。
3.开始fuzz,选择一个或者多个fuzz 对象开始进行模糊测试,以 fuzz_image_store
为例,其中 --corpus-dir
参数可以指定种子目录,不加该参数默认以空语料库进行fuzz。
在oss-fuzz中,对于go语言模糊测试默认使用 go-fuzz
来编译harness,之后使用libfuzzer
作为引擎进行fuzz,fuzz开始后会在out文件夹下生成一个文件夹,用来存放相关输出。
经过漫长的等待之后可能会发生崩溃,
崩溃信息如下:
go-fuzz-header:
https://github.com/AdaLogics/go-fuzz-headers
https://adalogics.com/blog/structure-aware-go-fuzzing-complex-types
oss-fuzz教程
https://n0va-scy.github.io/2022/02/14/oss-fuzz%E5%88%9D%E6%8E%A2/
https://github.com/google/oss-fuzz/blob/master/docs/getting-started/new_project_guide.md
containerd
https://github.com/containerd/containerd/blob/11de19af68c7d21c8fe01058026257ecd5d6ed13/contrib/fuzz/oss_fuzz_build.sh
homepage:
"https://github.com/containerd/containerd"
main_repo:
"https://github.com/containerd/containerd"
primary_contact:
"security@containerd.io"
auto_ccs :
-
"adam@adalogics.com"
language: go
fuzzing_engines:
-
libfuzzer
sanitizers:
-
address
homepage:
"https://github.com/containerd/containerd"
main_repo:
"https://github.com/containerd/containerd"
primary_contact:
"security@containerd.io"
auto_ccs :
-
"adam@adalogics.com"
language: go
fuzzing_engines:
-
libfuzzer
sanitizers:
-
address
FROM gcr.io
/
oss
-
fuzz
-
base
/
base
-
builder
-
go
RUN apt
-
get update && apt
-
get install
-
y btrfs
-
progs libc
-
dev pkg
-
config libseccomp
-
dev gcc wget libbtrfs
-
dev
RUN git clone
-
-
depth
1
https:
/
/
github.com
/
containerd
/
containerd
RUN git clone
-
-
depth
1
https:
/
/
github.com
/
cncf
/
cncf
-
fuzzing
COPY build.sh $SRC
/
WORKDIR $SRC
/
containerd
FROM gcr.io
/
oss
-
fuzz
-
base
/
base
-
builder
-
go
RUN apt
-
get update && apt
-
get install
-
y btrfs
-
progs libc
-
dev pkg
-
config libseccomp
-
dev gcc wget libbtrfs
-
dev
RUN git clone
-
-
depth
1
https:
/
/
github.com
/
containerd
/
containerd
RUN git clone
-
-
depth
1
https:
/
/
github.com
/
cncf
/
cncf
-
fuzzing
COPY build.sh $SRC
/
WORKDIR $SRC
/
containerd
.
/
buildconf.sh
.
/
configure
make clean
make
-
j$(nproc)
all
$CXX $CXXFLAGS
-
std
=
c
+
+
11
-
Ilib
/
\
$SRC
/
parse_fuzzer.cc
-
o $OUT
/
parse_fuzzer \
$LIB_FUZZING_ENGINE .libs
/
libexpat.a
cp $SRC
/
*
.
dict
$SRC
/
*
.options $OUT
/
.
/
buildconf.sh
.
/
configure
make clean
make
-
j$(nproc)
all
$CXX $CXXFLAGS
-
std
=
c
+
+
11
-
Ilib
/
\
$SRC
/
parse_fuzzer.cc
-
o $OUT
/
parse_fuzzer \
$LIB_FUZZING_ENGINE .libs
/
libexpat.a
cp $SRC
/
*
.
dict
$SRC
/
*
.options $OUT
/
[Service]
Environment
=
"HTTP_PROXY=http://127.0.0.1:7890"
Environment
=
"HTTPS_PROXY=https://127.0.0.1:7890"
Environment
=
"NO_PROXY=127.0.0.1"
[Service]
Environment
=
"HTTP_PROXY=http://127.0.0.1:7890"
Environment
=
"HTTPS_PROXY=https://127.0.0.1:7890"
Environment
=
"NO_PROXY=127.0.0.1"
systemctl daemon
-
reload
systemctl restart docker
systemctl daemon
-
reload
systemctl restart docker
systemctl show docker
-
-
property
Environment
systemctl show docker
-
-
property
Environment
FROM gcr.io
/
oss
-
fuzz
-
base
/
base
-
builder
-
go
RUN apt
-
get update && apt
-
get install
-
y btrfs
-
progs libc
-
dev pkg
-
config libseccomp
-
dev gcc wget libbtrfs
-
dev
ENV HTTP_PROXY
"http://192.168.xx.xx:7890"
ENV HTTPS_PROXY
"http://192.168.xx.xx:7890"
RUN git clone https:
/
/
github.com
/
containerd
/
containerd
WORKDIR containerd
RUN git checkout
-
b remotes
/
origin
/
release
/
1.6
remotes
/
origin
/
release
/
1.6
WORKDIR $SRC
RUN git clone
-
-
depth
1
https:
/
/
github.com
/
cncf
/
cncf
-
fuzzing
COPY build.sh $SRC
/
WORKDIR $SRC
/
containerd
FROM gcr.io
/
oss
-
fuzz
-
base
/
base
-
builder
-
go
RUN apt
-
get update && apt
-
get install
-
y btrfs
-
progs libc
-
dev pkg
-
config libseccomp
-
dev gcc wget libbtrfs
-
dev
ENV HTTP_PROXY
"http://192.168.xx.xx:7890"
ENV HTTPS_PROXY
"http://192.168.xx.xx:7890"
RUN git clone https:
/
/
github.com
/
containerd
/
containerd
WORKDIR containerd
RUN git checkout
-
b remotes
/
origin
/
release
/
1.6
remotes
/
origin
/
release
/
1.6
WORKDIR $SRC
RUN git clone
-
-
depth
1
https:
/
/
github.com
/
cncf
/
cncf
-
fuzzing
COPY build.sh $SRC
/
WORKDIR $SRC
/
containerd
cd
/
path
/
to
/
oss
-
fuzz
python infra
/
helper.py build_image containerd
cd
/
path
/
to
/
oss
-
fuzz
python infra
/
helper.py build_image containerd
python infra
/
helper.py build_fuzzers containerd
python infra
/
helper.py build_fuzzers containerd
package fuzz
import
(
fuzz
"github.com/AdaLogics/go-fuzz-headers"
runtime
"k8s.io/cri-api/pkg/apis/runtime/v1"
"github.com/containerd/containerd/pkg/cri/server"
)
func FuzzParseAuth(data []byte)
int
{
f :
=
fuzz.NewConsumer(data)
auth :
=
&runtime.AuthConfig{}
err :
=
f.GenerateStruct(auth)
if
err !
=
nil {
return
0
}
host, err :
=
f.GetString()
if
err !
=
nil {
return
0
}
_, _, _
=
server.ParseAuth(auth, host)
return
1
}
package fuzz
import
(
fuzz
"github.com/AdaLogics/go-fuzz-headers"
runtime
"k8s.io/cri-api/pkg/apis/runtime/v1"
"github.com/containerd/containerd/pkg/cri/server"
)
func FuzzParseAuth(data []byte)
int
{
f :
=
fuzz.NewConsumer(data)
auth :
=
&runtime.AuthConfig{}
err :
=
f.GenerateStruct(auth)
if
err !
=
nil {
return
0
}
host, err :
=
f.GetString()
if
err !
=
nil {
return
0
}
_, _, _
=
server.ParseAuth(auth, host)
return
1
}
python infra
/
helper.py run_fuzzer
-
-
corpus
-
dir
=
.
/
build
/
out
/
containerd
/
corpus containerd fuzz_image_store
python infra
/
helper.py run_fuzzer
-
-
corpus
-
dir
=
.
/
build
/
out
/
containerd
/
corpus containerd fuzz_image_store
runtime: unexpected
return
pc
for
runtime.gopark called
from
0x0
stack: frame
=
{sp:
0x10c000078f40
, fp:
0x10c000078f60
} stack
=
[
0x10c000078000
,
0x10c000079000
)
0x000010c000078e40
:
0x0000000000000000
0x0000000000000000
0x000010c000078e50
:
0x0000000000000000
0x0000000000000000
0x000010c000078e60
:
0x7a75662f706d742f
0x3833303039332d7a
0x000010c000078e70
:
0x39332d7a7a75662f
0x3430383538333030
0x000010c000078e80
:
0xdef0995b8d5812aa
0x758f15f0dcd67525
0x000010c000078e90
:
0xee5d5b00aa1475d6
0x1d3fd1a2d44b0579
0x000010c000078ea0
:
0x0000000000000000
0x0000000000000000
0x000010c000078eb0
:
0x0000006901000000
0x0000000000000000
0x000010c000078ec0
:
0x0000000000000000
0x0000000000000000
0x000010c000078ed0
:
0x0000000000070000
0x0000000000000000
0x000010c000078ee0
:
0xffffffffffffffff
0x00ffffffffffffff
0x000010c000078ef0
:
0x000010c0001ddb80
0x000010c0005a3600
0x000010c000078f00
:
0x000010c0004651e0
0x000010c000465340
0x000010c000078f10
:
0x000010c0001dc420
0x000010c0003920e0
0x000010c000078f20
:
0x000010c0001948d0
0x000010c0001906b0
0x000010c000078f30
:
0x000010c000582f90
0x000010c000582fd0
0x000010c000078f40
: <
0x000010c0005837d0
0x000010c000190590
0x000010c000078f50
:
0x0000000000000000
!
0x0000000000000000
0x000010c000078f60
: >
0x000093f73283d9b8
0x000010c0001282c0
0x000010c000078f70
:
0x0000000000001418
0x0000000000000000
0x000010c000078f80
:
0x0000000000000000
0x0000000000000000
0x000010c000078f90
:
0x00000a8c46505853
0x0000000000000207
0x000010c000078fa0
:
0x0000000000000a88
0x0000000000000000
0x000010c000078fb0
:
0x0000000000000000
0x0000000000000000
0x000010c000078fc0
:
0x0000000000000203
0x0000000000000000
0x000010c000078fd0
:
0x0000000000000000
0x0000000000000000
0x000010c000078fe0
:
0x0000000000000000
0x0000000000000000
0x000010c000078ff0
:
0x0000000000000000
0x0000000000000000
fatal error: unknown caller pc
runtime stack:
runtime.throw({
0x1f3f3fb
,
0x328e0e0
})
runtime
/
panic.go:
1198
+
0x71
runtime.gentraceback(
0x7f220a620c90
,
0x1
,
0x0
,
0x7f220a620b30
,
0x0
,
0x0
,
0x7fffffff
,
0x7f220a620c90
,
0x0
,
0x0
)
runtime
/
traceback.go:
274
+
0x1956
runtime.scanstack(
0x10c000001ba0
,
0x10c000051698
)
runtime
/
mgcmark.go:
748
+
0x197
runtime.markroot.func1()
runtime
/
mgcmark.go:
232
+
0xb1
runtime.markroot(
0x10c000051698
,
0x1f
)
runtime
/
mgcmark.go:
205
+
0x170
runtime.gcDrain(
0x10c000051698
,
0x3
)
runtime
/
mgcmark.go:
1013
+
0x379
runtime.gcBgMarkWorker.func2()
runtime
/
mgc.go:
1269
+
0xa5
runtime.systemstack()
runtime
/
asm_amd64.s:
383
+
0x46
goroutine
6
[GC worker (idle)]:
runtime.systemstack_switch()
runtime
/
asm_amd64.s:
350
fp
=
0x10c00006af60
sp
=
0x10c00006af58
pc
=
0x5c4a20
runtime.gcBgMarkWorker()
runtime
/
mgc.go:
1256
+
0x1b3
fp
=
0x10c00006afe0
sp
=
0x10c00006af60
pc
=
0x5790b3
runtime.goexit()
runtime
/
asm_amd64.s:
1581
+
0x1
fp
=
0x10c00006afe8
sp
=
0x10c00006afe0
pc
=
0x5c6cc1
created by runtime.gcBgMarkStartWorkers
runtime
/
mgc.go:
1124
+
0x25
goroutine
17
[runnable, locked to thread]:
runtime.goexit()
runtime
/
asm_amd64.s:
1581
+
0x1
goroutine
7
[chan receive]:
k8s.io
/
klog
/
v2.(
*
loggingT).flushDaemon(
0x0
)
k8s.io
/
klog
/
v2@v2.
30.0
/
klog.go:
1181
+
0x8b
created by k8s.io
/
klog
/
v2.init.
0
k8s.io
/
klog
/
v2@v2.
30.0
/
klog.go:
420
+
0x115
AddressSanitizer:DEADLYSIGNAL
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
12
=
=
ERROR: AddressSanitizer: ABRT on unknown address
0x00000000000c
(pc
0x0000005c85e1
bp
0x7f220a620678
sp
0x7f220a620660
T8)
SCARINESS:
10
(signal)
DEDUP_TOKEN: runtime.
raise
.abi0
-
-
runtime.crash
-
-
runtime.fatalthrow.func1
AddressSanitizer can
not
provide additional info.
SUMMARY: AddressSanitizer: ABRT runtime
/
sys_linux_amd64.s:
165
in
runtime.
raise
.abi0
Thread T8 created by T3 here:
DEDUP_TOKEN: __interceptor_pthread_create
-
-
_cgo_try_pthread_create
-
-
runtime.newm
Thread T3 created by T1 here:
DEDUP_TOKEN: __interceptor_pthread_create
-
-
_cgo_try_pthread_create
-
-
runtime.newm
Thread T1 created by T0 here:
DEDUP_TOKEN: __interceptor_pthread_create
-
-
_cgo_try_pthread_create
-
-
x_cgo_sys_thread_create
=
=
12
=
=
ABORTING
MS:
2
EraseBytes
-
ChangeBinInt
-
; base unit: feb33bf726c50d41c5dc2c8cea890cb18040c1f8
0x10
,
0xd
,
0xb
,
0x3b
,
0x2
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x84
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x8
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x3
,
0xfa
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
0x0
,
\
020
\
015
\
013
;\
002
\
000
\
000
\
000
\
000
\
000
\
204
\
000
\
000
\
000
\
000
\
000
\
000
\
000
\
000
\
000
\
000
\
000
\
000
\
000
\
000
\
000
\
000
\
000
\
000
\
000
\
000
\
000
\
000
\
000
\
000
\
000
\
000
\
000
\
000
\
000
\
000
\
010
\
000
\
000
\
000
\
000
\
000
\
003
\
372
\
000
\
000
\
000
\
000
\
000
\
000
\
000
\
000
\
000
\
000
\
000
\
000
artifact_prefix
=
'./'
; Test unit written to .
/
crash
-
66c182f8f6dac7209a14e631d117b0879331cbfe
Base64: EA0LOwIAAAAAAIQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAD
+
gAAAAAAAAAAAAAAAA
=
=
runtime: unexpected
return
pc
for
runtime.gopark called
from
0x0
stack: frame
=
{sp:
0x10c000078f40
, fp:
0x10c000078f60
} stack
=
[
0x10c000078000
,
0x10c000079000
)
0x000010c000078e40
:
0x0000000000000000
0x0000000000000000
0x000010c000078e50
:
0x0000000000000000
0x0000000000000000
0x000010c000078e60
:
0x7a75662f706d742f
0x3833303039332d7a
0x000010c000078e70
:
0x39332d7a7a75662f
0x3430383538333030
0x000010c000078e80
:
0xdef0995b8d5812aa
0x758f15f0dcd67525
0x000010c000078e90
:
0xee5d5b00aa1475d6
0x1d3fd1a2d44b0579
0x000010c000078ea0
:
0x0000000000000000
0x0000000000000000
0x000010c000078eb0
:
0x0000006901000000
0x0000000000000000
0x000010c000078ec0
:
0x0000000000000000
0x0000000000000000
0x000010c000078ed0
:
0x0000000000070000
0x0000000000000000
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2022-5-27 00:10
被ZxyNull编辑
,原因: