CVE-2021-23840整数溢出漏洞

漏洞概述

该漏洞为公开漏洞,漏洞编号为CVE-2021-3449,漏洞类型为空指针引用漏洞,存在于OpenSSL的1.1.1-1.1.1i版本,包含11.1.4目标PA设备中的开源组件OpenSSL版本1.1.1g。官方描述信息如下:

漏洞原理

比对修复版本,在修复版本增加的内容check如下:

根据其注释,要求(inl-j) & ~(bl -1)的值不能超过 INT_MAX - bl。

存在漏洞代码如下,在漏洞函数evp_EncryptDecryptUpdate内第350行进入if(i !=0)分支,在第351行进入else分支,此时openssl会进行加密算法的padding部分,即将不是规定分组大小整数倍的长度补充到分组大小整数倍。此时会将*outl += bl对outl加上一个分组大小。跳出350行的if-else分支后,将输入长度除去不足分组大小的部分加到*outl中,因此如果inl本就接近INT_MAX,此时会发生整数溢出导致*outl变为INT_MAX即(0x80000000),发生整数溢出。

注意触发该漏洞需要保证 i 不为0,也就是说ctx->buf不为空,那么就需要在执行EVP_EncryptUpdate前再执行一次不是分组大小整数倍的EVP_EncryptUpdate,并且需要保证inl & 0xf >= j,防止作差时向上借位。

构造libafl harness程序如下:

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <openssl/evp.h>
#include <assert.h>
#include <unistd.h>

// 条件编译 LibAFL 宏
#ifdef USE_LIBAFL
__AFL_FUZZ_INIT();
#endif

int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
const uint8_t key[16] = {0x80, 0xc7, 0x9e, 0x14, 0xef, 0x09, 0xbd, 0x42, 0x0b, 0x1f, 0x80, 0x98, 0x82, 0x6b, 0x12, 0xfd};
const uint8_t iv[16] = {0x80, 0xc7, 0x9e, 0x14, 0xef, 0x09, 0xbd, 0x42, 0x0b, 0x1f, 0x80, 0x98, 0x82, 0x6b, 0x12, 0xfd};
char dest[1024] = {0};

size_t data_size = size;
if (size >= 4)
{
size = ((uint32_t *) data)[0];
}

FILE *fp = fopen("debug.log", "a");
if (fp) {
fprintf(fp, "input size: %zu\n", size);
// fclose(fp);
}

uint8_t *msg = NULL;
if (size > 0) {
msg = (uint8_t*)malloc(size);
if (!msg) return 0;
for (size_t i = 0; i < size; i++) {
msg[i] = data[i % data_size];
}
}

const EVP_CIPHER* cipher = EVP_aes_128_cbc();
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
assert(ctx != NULL);

size_t out_size = size;
size_t blockSize = EVP_CIPHER_block_size(cipher);
size_t out_len1 = out_size + blockSize;
uint8_t *out = (uint8_t*)malloc(out_len1);

if (!out) {
if (msg) free(msg);
EVP_CIPHER_CTX_free(ctx);
return 0;
}

size_t len1 = 0, len2 = 0;

assert(EVP_EncryptInit_ex(ctx, cipher, NULL, key, iv) == 1);
// make ctx->buf_len != 0
unsigned char dummy_data[10] = {0}; // Dummy data to populate buffer
assert(EVP_EncryptUpdate(ctx, out, &len1, dummy_data, sizeof(dummy_data)) == 1);

if (size > 0 && msg) {
assert(EVP_EncryptUpdate(ctx, out, &len1, msg, size) == 1);
} else {
len1 = 0;
}

assert(EVP_EncryptFinal_ex(ctx, out + len1, &len2) == 1);

size_t total_len = len1 + len2;
if (fp) {
fprintf(fp, "total_len: %zu\n", total_len);
fclose(fp);
}
assert(total_len > 0);

if (msg) free(msg);
free(out);
EVP_CIPHER_CTX_free(ctx);

return 0;
}

int main() {
const char* input_file = getenv("FUZZ_INPUT_FILE");
if (input_file) {
FILE *fp = fopen(input_file, "rb");
if (!fp) {
perror("fopen");
return 1;
}
fseek(fp, 0, SEEK_END);
long filesize = ftell(fp);
fseek(fp, 0, SEEK_SET);
if (filesize <= 0) {
fclose(fp);
return 1;
}
uint8_t *buf = malloc(filesize);
if (!buf) {
fclose(fp);
return 1;
}
fread(buf, 1, filesize, fp);
fclose(fp);
LLVMFuzzerTestOneInput(buf, filesize);
free(buf);
return 0;
}
#ifdef USE_LIBAFL
// LibAFL 模式
__AFL_INIT();
unsigned char *buf = __AFL_FUZZ_TESTCASE_BUF;
size_t len = __AFL_FUZZ_TESTCASE_LEN;
LLVMFuzzerTestOneInput(buf, len);
#else
// 标准模式
uint8_t buffer[65536];
ssize_t result = read(STDIN_FILENO, buffer, sizeof(buffer));
if (result > 0) {
LLVMFuzzerTestOneInput(buffer, result);
} else {
const uint8_t test_data[] = {0x41, 0x42, 0x43, 0x44};
LLVMFuzzerTestOneInput(test_data, sizeof(test_data));
}
#endif
return 0;
}

构造种子以及编译harness命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 构造crash种子
echo -e -n "\\xfe\\xff\\xff\\x7f" > huge_seeds
# compile harness with libafl_cc
"${PROJECT_ROOT}/target/release/libafl_cc" \
-DUSE_LIBAFL \
-fsanitize=address -fsanitize=undefined -fno-sanitize-recover=undefined \
-I"${PROJECT_ROOT}/source_tests/vulnerable_openssl/openssl-1.1.1g/include" \
"${SCRIPT_DIR}/harness.c" \
-L"${PROJECT_ROOT}/source_tests/vulnerable_openssl/openssl-1.1.1g" \
-lssl -lcrypto \
-o "${SCRIPT_DIR}/harness_libafl"

# start fuzz
"${PROJECT_ROOT}/target/release/fuzzer-palo" fuzz-libsource --in "${SCRIPT_DIR}/seeds" --out "${SCRIPT_DIR}/outs" "${SCRIPT_DIR}/harness_libafl"

成功触发整数溢出:

但是fuzz时跑不出结果,因为需要申请超大内存,需要增大超时时间,保证目标程序跑完一轮后再退出。