libcurl SOCKS5 GSS-API竞争条件漏洞分析

本文详细分析了libcurl中SOCKS5 GSS-API认证过程中存在的竞争条件漏洞,该漏洞源于全局gss_context变量在多线程环境下缺乏同步机制,导致数据竞争和未定义行为,可能引发进程崩溃和认证失败。

Race condition on global gss_context during SOCKS5 GSS-API negotiation in libcurl

摘要

并发SOCKS5 GSS-API认证在无同步情况下共享文件作用域的全局gss_context,导致数据竞争和未定义行为。

漏洞详情

全局上下文定义位置

1
static gss_ctx_id_t gss_context = GSS_C_NO_CONTEXT;

协商期间通过地址传递给GSS初始化例程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
gss_major_status = Curl_gss_init_sec_context(data,
                                             &gss_minor_status,
                                             &gss_context,
                                             server,
                                             &Curl_krb5_mech_oid,
                                             NULL,
                                             gss_token,
                                             &gss_send_token,
                                             TRUE,
                                             &gss_ret_flags);

验证过程

使用Helgrind和libcurl的DEBUGBUILD GSS存根(CURL_STUB_GSS_CREDS=KRB5)观察到了可重现的数据竞争,直接引用全局符号gss_context:

Helgrind证据

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
==XXXX== Possible data race during read of size 8 at ...
==XXXX==    at 0x1CEFC0: stub_gss_init_sec_context (curl_gssapi.c:126)
==XXXX==    by 0x1CEE43: Curl_gss_init_sec_context (curl_gssapi.c:329)
==XXXX==    by 0x1BCB3B: Curl_SOCKS5_gssapi_negotiate (socks_gssapi.c:184)
...
==XXXX== This conflicts with a previous write of size 8 by thread #Y
==XXXX==    at 0x1CF4A4: stub_gss_init_sec_context (curl_gssapi.c:264)
==XXXX==    by 0x1CEE43: Curl_gss_init_sec_context (curl_gssapi.c:329)
==XXXX==    by 0x1BCB3B: Curl_SOCKS5_gssapi_negotiate (socks_gssapi.c:184)
==XXXX==  Address ... is 0 bytes inside data symbol "gss_context"

受影响版本

  • 从当前master构建(curl版本:8.17.0-DEV)
  • Ubuntu 24.04(arm64)容器环境
  • GSS-API提供者:MIT Kerberos(系统libgssapi_krb5)

重现步骤

1. 启动Ubuntu容器并安装依赖

1
2
3
4
5
6
7
docker run --rm -it -v "$PWD":/src -w /src ubuntu:24.04 bash -lc '
  set -euo pipefail
  export DEBIAN_FRONTEND=noninteractive
  apt-get update -qq
  apt-get install -y -qq clang make autoconf automake libtool pkg-config libkrb5-dev python3 valgrind > /dev/null
  update-ca-certificates > /dev/null 2>&1 || true
'

2. 配置和构建libcurl

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
docker run --rm -it -v "$PWD":/src -w /src ubuntu:24.04 bash -lc '
  set -euo pipefail
  if [ -f Makefile ]; then make distclean || true; fi
  if [ -x ./buildconf ]; then ./buildconf; else autoreconf -fi; fi
  export CC=clang CFLAGS="-O0 -g -fno-omit-frame-pointer" LDFLAGS=""
  ./configure --enable-debug --with-gssapi --disable-shared \
    --without-ssl --without-libidn2 --without-libpsl --without-libssh2 \
    --without-brotli --without-zstd --without-nghttp2 --without-nghttp3 --without-ngtcp2
  make -j"$(nproc)"
'

3. 启动支持GSS-API的SOCKS5代理

 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
import socket, threading, struct

def handle(c):
    try:
        data=c.recv(262)
        if len(data)<3: return
        c.sendall(b"\x05\x01")
        hdr=c.recv(4)
        if len(hdr)<4: return
        ln=struct.unpack("!H", hdr[2:4])[0]
        _=c.recv(ln)
        c.sendall(b"\x01\x01\x00\x01D")
        hdr=c.recv(4)
        if len(hdr)<4: return
        ln=struct.unpack("!H", hdr[2:4])[0]
        _=c.recv(ln)
        c.sendall(b"\x01\x02\x00\x01\x00")
    finally:
        try: c.close()
        except: pass

s=socket.socket(); s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
s.bind(("0.0.0.0",1081)); s.listen()
print("listening 0.0.0.0 1081", flush=True)
while True:
    c,_=s.accept()
    threading.Thread(target=handle,args=(c,),daemon=True).start()

4. 构建多线程PoC

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#include <pthread.h>
#include <curl/curl.h>
static void *worker(void *u){
  CURL *e=curl_easy_init();
  curl_easy_setopt(e,CURLOPT_URL,(char*)u);
  curl_easy_setopt(e,CURLOPT_PROXY,"socks5://127.0.0.1:1081");
  curl_easy_setopt(e,CURLOPT_SOCKS5_GSSAPI_SERVICE,"rcmd");
  curl_easy_setopt(e,CURLOPT_SOCKS5_AUTH,(long)CURLAUTH_GSSAPI);
  for(int i=0;i<50;++i) curl_easy_perform(e);
  curl_easy_cleanup(e);
  return 0;
}
int main(void){
  curl_global_init(CURL_GLOBAL_DEFAULT);
  enum { N=16 }; pthread_t th[N];
  for(int i=0;i<N;++i) pthread_create(&th[i],0,worker,"http://example.com");
  for(int i=0;i<N;++i) pthread_join(th[i],0);
  curl_global_cleanup();
  return 0;
}

5. 在Helgrind下运行测试

1
2
3
4
5
docker run --rm -it -v "$PWD":/src -w /src ubuntu:24.04 bash -lc '
  set -euo pipefail
  export CURL_STUB_GSS_CREDS=KRB5
  valgrind --tool=helgrind --quiet --fair-sched=try ./test_gss_race | tee docker_helgrind.log
'

影响分析

共享的全局gss_context在连接间无同步地并发访问和修改,存在以下实际风险:

  • 由于GSS上下文处理中的未定义行为和竞争导致进程崩溃/拒绝服务
  • 在高负载下SOCKS5代理协商期间出现认证失败或异常行为

未观察到内存泄露或远程代码执行,已验证的影响是在多句柄/认证并发运行时与并发相关的不稳定性(DoS/UB)。

修复方案

建议将gss_context设为Curl_SOCKS5_gssapi_negotiate()的局部变量(如Windows SSPI中所示),并在所有退出时删除。这与其他curl GSS路径(SPNEGO/SASL使用每个连接的上下文)相匹配。

状态更新

该问题已通过PR #18711修复,验证显示Helgrind不再报告socks_gssapi.c/curl_gssapi.c中的竞争条件。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计