2.5. 合约漏洞检测

C++ 语言本身存在大量的非内存安全行为和未定义行为,这些行为在合约中可能导致难以定位的错误。

xdev 提供了相关运行时漏洞的检测能力

2.5.1. 准备工作

漏洞检测需要使用较新的 xdev, 版本不低于 v1.1.0

2.5.2. 检测未定义行为

编写异常代码如下

1
2
3
4
5
6
DEFINE_METHOD(SanitizerCase, undefined_behavior) {
xchain::Context* ctx = self.context();
int *a = 0, b;
b = *a;
ctx->ok("ok");
}

可以看到,在代码中存在空指针解引用的,这段代码的具体行为会随着编译器,运行时甚至是编译参数的不同而不同。

执行如下命令以debug 模式构建合约

1
$ xdev build --build-mode=debug

注解

使用 debug 模式构建的合约主要用于本地单元测试等场景, 请勿将debug 模式构建的合约部署到生产环境

编写对应的测试代码如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Test("undefined_behavior", function (t) {
    var contract;
    t.Run("deploy", function (tt) {
        contract = xchain.Deploy({
            name: "sanitizer_case",
            code: "../sanitizer_case.wasm",
            lang: "c",
            init_args: {},
            options: { "account": "XC1111111111111111@xuper" }
        })
    });

    t.Run("invoke", function (tt) {
        resp = contract.Invoke("undefined_behavior", {});
    })
})

通过xdev 执行单元测试,可以发现不仅运行了对应的单元测试,还给出了未定义行为的提示

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
$ xdev test -r undefined_behavior
=== RUN   undefined_behavior
=== RUN   undefined_behavior/deploy
=== RUN   undefined_behavior/invoke
/Users/chenfengjin/baidu/contract-sdk-cpp/example/sanitizer_case/src/main.cc:16:9: runtime error: load of null pointer of type 'int'SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /Users/chenfengjin/baidu/contract-sdk-cpp/example/sanitizer_case/src/main.cc:16:9 in
=================================================================
==42==ERROR: AddressSanitizer: null-pointer-dereference on address 0x00000000 at pc 0x00000000 bp 0x014ecf70 sp 0x014ecf7cREAD of size 4 at 0x00000000 thread T0Address 0x00000000 is located in the shadow gap area.
SUMMARY: AddressSanitizer: null-pointer-dereference (<unknown module>)
==42==ABORTING
    value.go:476: Exception: exec: &{exit}
--- FAIL: undefined_behavior (1.17s)
    --- PASS: undefined_behavior/deploy (0.48s)
    --- FAIL: undefined_behavior/invoke (0.68s)
FAIL
Error:

2.5.3. 检测内存异常

编写异常合约代码如下

1
2
3
4
5
6
7
DEFINE_METHOD(SanitizerCase, buffer_overflow) {
    xchain::Context* ctx = self.context();
    int a[10];
    int b = 10;
    int c = a[b];
    ctx->ok("ok");
}

在该代码中的第五行存在缓冲区溢出问题,该问题在运行时会导致不可预期的问题且很难通过常规的测试手段发现。

编写对应的测试代码如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Test("buffer_overflow", function (t) {
    var contract;
    t.Run("deploy", function (tt) {
        contract = xchain.Deploy({
            name: "sanitizer_case",
            code: "../sanitizer_case.wasm",
            lang: "c",
            init_args: {},
            options: { "account": "XC1111111111111111@xuper" }
        })
    });

    t.Run("invoke", function (tt) {
        resp = contract.Invoke("buffer_overflow", {});
    })
})

执行如下命令运行单元测试

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
$xdev test -r buffer_overflow
=== RUN   buffer_overflow
=== RUN   buffer_overflow/deploy
=== RUN   buffer_overflow/invoke
/Users/chenfengjin/baidu/contract-sdk-cpp/example/sanitizer_case/src/main.cc:24:13: runtime error: index 10 out of bounds for type 'int[10]'SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /Users/chenfengjin/baidu/contract-sdk-cpp/example/sanitizer_case/src/main.cc:24:13 in
=================================================================
==42==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x014ecf78 at pc 0x00000000 bp 0x014ecf30 sp 0x014ecf3cREAD of size 4 at 0x014ecf78 thread T0Address 0x014ecf78 is a wild pointer.
SUMMARY: AddressSanitizer: stack-buffer-overflow (<unknown module>)
Shadow bytes around the buggy address:
0x0029d990: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0029d9a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0029d9b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0029d9c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0029d9d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0029d9e0: 00 00 00 00 00 00 00 00 f1 f1 00 00 00 00 00[f2]
0x0029d9f0: f2 f2 f2 f2 f8 f8 f3 f3 00 00 00 00 f1 f1 00 f3
0x0029da00: f3 f3 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0029da10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0029da20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0029da30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable:           00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone:       fa
Freed heap region:       fd
Stack left redzone:      f1
Stack mid redzone:       f2
Stack right redzone:     f3
Stack after return:      f5
Stack use after scope:   f8
Global redzone:          f9
Global init order:       f6
Poisoned by user:        f7
Container overflow:      fc
Array cookie:            ac
Intra object redzone:    bb
ASan internal:           fe
Left alloca redzone:     ca
Right alloca redzone:    cb
Shadow gap:              cc
==42==ABORTING
    value.go:476: Exception: exec: &{exit}
--- FAIL: buffer_overflow (1.37s)
    --- PASS: buffer_overflow/deploy (0.48s)
    --- FAIL: buffer_overflow/invoke (0.88s)
FAIL
Error:

可以看到,在执行单元测试过程中,明确给出了存在缓冲区溢出的行为,并给出了对应的行号和列号。

可以在 github 上找到:完整的漏洞示例代码和测试文件

2.5.4. 其他未定义行为和内存异常

除了支持空指针接引用和缓冲区溢出,xdev 也支持其他的未定义行为和内存异常问题的检测,如 Use After Free, Use After Return,空指针赋值等等。