
libcurl の SMB プロトコルハンドラに use-after-free の脆弱性を発見しました。2 回目の SMB 転送が同じサーバーへの既存の接続を再利用すると、新しいリクエストのファイルパス(req->path)は解放されたヒープメモリを指すダングリングポインタになります。このメモリは strlen() によって読み取られ、送信される SMB パケットにコピーされるため、ヒープの内容がサーバーに漏えいしたり、クラッシュしたりします。
一行修正: req->path が needle の smbc->share から借用するのではなく、自身のコピーを保持するようにします。
| CVE | CVE-2026-3805 |
| 脆弱性クラス | Use-After-Free (CWE-416) |
| 根本原因 | req->path が needle の smbc->share を指しており、接続再利用時に解放される |
| 混入 | 777c5209df(2025-04-30) - curl 8.13.0 |
| 修正 | e090be9f73a7a71459ef678c - curl 8.19.0(2026年3月11日) |
| 影響を受けるバージョン | curl 8.13.0 から 8.18.0 |
| 影響 | サーバーへのヒープ情報漏えい、クラッシュ |
| 深刻度 | 7.5 - 高 - CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H |
smb_setup_connection() は、キャッシュ検索に使用される一時的な「needle」接続上で実行されます。req->path を smbc->share(needle が所有するヒープメモリ)の内部を指すように設定します。接続キャッシュが再利用可能な接続を見つけると、needle は破棄され(smbc->share が解放される)、req->path は easy handle 上に残り、ダングリングポインタになります。
smb_send_open() が SMB OPEN リクエストを構築する際、strlen(req->path) を呼び出し、その結果を送信パケットにコピーします。解放されたヒープ領域にあるデータはすべてサーバーに送信されます。攻撃者がサーバーを制御している場合(SSRF、または悪意のある SMB に接続するユーザー)、SMB NT_CREATE_ANDX リクエストの「ファイル名」として漏えいしたヒープの内容を受け取ります。
libcurl はパフォーマンス向上のため、積極的に接続を再利用します。同じホストに複数のリクエストを送信する場合、curl は新しい接続を確立する代わりに、既存の接続を再利用できるかどうかを確認します。この仕組みは次のように動作します:
SMB プロトコルハンドラは状態を 2 か所に保存します:
smbc(接続状態): 接続の meta_hash 上req(リクエスト状態): easy handle の meta 上バグ: needle のセットアップ中に req->path が smbc->share の内部を指すように設定されます。再利用時に needle が破棄されると smbc->share は解放されますが、req->path は依然としてそこを指しています。
smb_parse_url_path() では、コードが SMB URL パスを解析し、共有名とファイルパスに分割します:
// lib/smb.c, smb_parse_url_path() line 431:
smbc->share = curlx_strdup((*path == '/' || *path == '\\') ? path + 1 : path);
// ...
*slash++ = 0;
req->path = slash; // <--- points into smbc->share on the NEEDLE
URL smb://server/share1/file1.txt の場合、次のものが作成されます:
smbc->share = "share1\0file1.txt"(ヒープ割り当て、needle が所有)req->path = "file1.txt" へのポインタ(smbc->share の内部)接続再利用がトリガーされると:
// lib/url.c, url_find_or_create_conn() line 3619:
out:
if(needle)
Curl_conn_free(data, needle); // Destroys needle -> frees smbc->share
Curl_conn_free() は Curl_hash_destroy(&conn->meta_hash) を呼び出し、これが smb_conn_dtor() を起動して smbc->share を解放します。しかし、req->path(生存する easy handle 上にある)は、解放されたメモリを依然として指しています。
SMB リクエストが進むと:
// lib/smb.c, smb_send_open() line 750-769:
const size_t byte_count = strlen(req->path) + 1; // UAF READ
// ...
curlx_strcopy(msg.bytes, sizeof(msg.bytes), req->path, byte_count - 1); // UAF READ
解放されたヒープメモリは再割り当てされ、機密データを含んでいる可能性があります。strlen(req->path) はヌルバイトに到達するまで前方を走査し、curlx_strcopy() はその内容をサーバーに送信される SMB パケットにコピーします。
攻撃シナリオ: 攻撃者が SMB サーバーを制御する SSRF。被害者アプリケーションは攻撃者のサーバーに 2 つの SMB リクエストを送信します。2 番目のリクエストは、SMB OPEN リクエストの「ファイル名」としてヒープの内容を漏えいさせます。
解放されたメモリが OS に返却されている場合(マップ解除)、strlen() は SIGSEGV / アクセス違反を引き起こします。
UAF がなくても、SMB 接続再利用は意味的に壊れています。smb_send_tree_connect() は、新しいリクエストの共有ではなく、再利用された接続(古い共有)の smbc->share を使用します。TREE_CONNECT は完全に誤った共有に送信されます。
# Two SMB URLs to the same server, different shares/files:
curl smb://192.168.1.100/share1/file1.txt -o /dev/null \
smb://192.168.1.100/share2/file2.txt -o /dev/null
CURLM *multi = curl_multi_init();
CURL *e1 = curl_easy_init();
curl_easy_setopt(e1, CURLOPT_URL, "smb://server/share1/file1");
curl_multi_add_handle(multi, e1);
CURL *e2 = curl_easy_init();
curl_easy_setopt(e2, CURLOPT_URL, "smb://server/share2/file2");
curl_multi_add_handle(multi, e2);
// When e2 runs after e1 completes and reuses the connection: UAF
-fsanitize=address を指定して curl をビルドし、上記を実行します:
==PID==ERROR: AddressSanitizer: heap-use-after-free on address 0x...
READ of size 1 at 0x... thread T0
#0 strlen
#1 smb_send_open lib/smb.c:750
#2 smb_request_state lib/smb.c:1163
...
freed by thread T0 here:
#0 free
#1 smb_conn_dtor lib/smb.c:388
#2 Curl_hash_destroy
#3 Curl_conn_free lib/url.c:557
完全な再現スクリプトについては poc/REPRODUCE_UAF.sh を参照してください。
根本的な問題は、req->path が needle の smbc が所有するメモリへのポインタを借用していることです。修正では、req->path が自身のコピーを保持するようにします:
--- a/lib/smb.c
+++ b/lib/smb.c
@@ -378,7 +378,7 @@ static void smb_easy_dtor(void *key, size_t klen, void *entry)
(void)key;
(void)klen;
+ curlx_free(req->path);
curlx_free(req);
}
@@ -428,7 +428,10 @@ static CURLcode smb_parse_url_path(struct Curl_easy *data,
/* Parse the path for the file path converting any forward slashes into
backslashes */
*slash++ = 0;
- req->path = slash;
+ req->path = curlx_strdup(slash);
+ if(!req->path) {
+ Curl_safefree(smbc->share);
+ return CURLE_OUT_OF_MEMORY;
+ }
Stefan Eissing 氏が公式修正を e090be9f73a7a71459ef678c で実装しました。
開発者はダングリングポインタのリスクを部分的に認識していました。smb_easy_dtor() には次のコメントがあります:
/* `req->path` points to somewhere in `struct smb_conn` which is
* kept at the connection meta. If the connection is destroyed first,
* req->path points to free'd memory. */
しかし、これは接続が easy handle より先に破棄されるシナリオのみを考慮しています。実際のトリガーである接続再利用時の needle 破棄シナリオを見逃していました。
| 日付 | イベント |
|---|---|
| 2025-04-30 |
!defined(CURL_DISABLE_SMB)defined(USE_CURL_NTLM_CORE)sizeof(curl_off_t) > 4(64 ビットの off_t、ほとんどのプラットフォームで標準)NTLM サポートがある curl ビルドでは、SMB はデフォルトで有効です。
e090be9f73a7a71459ef678c777c5209dfCVE-2026-3805 - curl 8.19.0 で修正。影響を受けるバージョン: 8.13.0 から 8.18.0。
Daniel Wade - GitHub - [email protected]
777c5209df が SMB を meta hash ベースにリファクタリングし、バグが混入した |
| 2026-03-07 | セキュリティ監査中にバグを発見 |
| 2026-03-08 | HackerOne(#3591944)経由で curl に報告 |
| 2026-03-08 | curl が distros@openwall に連絡 |
| 2026-03-11 | 修正を含む curl 8.19.0 がリリースされ、CVE-2026-3805 が公開 |