
CVE-2026-3805: использование после освобождения (Use-After-Free) при повторном использовании SMB-соединения curl — раскрытие информации из кучи
Я обнаружил уязвимость типа use-after-free в обработчике протокола SMB в libcurl. Когда вторая SMB-передача переиспользует существующее соединение с тем же сервером, путь к файлу для нового запроса (req->path) является висячим указателем на освобождённую память в куче. Эта память читается через strlen() и копируется в исходящий SMB-пакет, что приводит к утечке содержимого кучи на сервер или к краху.
Исправление в одну строку: заставить req->path владеть собственной копией, а не заимствовать указатель из smbc->share иглы (needle).
| CVE | CVE-2026-3805 |
| Класс уязвимости | Use-After-Free (CWE-416) |
| Корневая причина | req->path указывает внутрь smbc->share иглы, освобождаемого при переиспользовании соединения |
| Появилась | 777c5209df (2025-04-30) — curl 8.13.0 |
| Исправлена | e090be9f73a7a71459ef678c — curl 8.19.0 (11 марта 2026) |
| Затронуты | curl 8.13.0 – 8.18.0 |
| Воздействие | Раскрытие содержимого кучи серверу, крах |
| Серьёзность | 7.5 - HIGH - 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 (память в куче, принадлежащая игле). Когда кэш соединений находит переиспользуемое соединение, игла уничтожается — smbc->share освобождается, — но req->path остаётся на easy handle, теперь уже висячий.
Когда smb_send_open() формирует запрос SMB OPEN, она вызывает strlen(req->path) и копирует результат в исходящий пакет. Любые данные, оказавшиеся в этой освобождённой области кучи, уходят на сервер. Если атакующий контролирует сервер (SSRF либо пользователь подключается к вредоносному SMB-серверу), он получает утёкшее содержимое кучи в качестве «имени файла» в запросе SMB NT_CREATE_ANDX.
libcurl агрессивно переиспользует соединения ради производительности. Когда вы выполняете несколько запросов к одному хосту, curl проверяет, можно ли переиспользовать существующее соединение, вместо установки нового. Механизм работает так:
Обработчик протокола SMB хранит состояние в двух местах:
smbc (состояние соединения) в meta_hash соединенияreq (состояние запроса) в meta easy handleОшибка: req->path устанавливается так, чтобы указывать внутрь smbc->share во время настройки иглы. Когда игла уничтожается при переиспользовании, smbc->share освобождается, но req->path по-прежнему указывает туда.
В smb_parse_url_path() код разбирает путь SMB URL и разделяет его на имя общего ресурса (share) и путь к файлу:
// 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" (выделено в куче, принадлежит игле)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-пакет, отправляемый на сервер.
Сценарий атаки: SSRF, при котором атакующий контролирует SMB-сервер. Приложение-жертва выполняет два SMB-запроса к серверу атакующего. Второй запрос сливает содержимое кучи как «имя файла» в запросе SMB OPEN.
Если освобождённая память была возвращена операционной системе (не отображена), 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
Соберите curl с флагом -fsanitize=address и выполните команды выше:
==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 заимствует указатель на память, принадлежащую 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. Был упущен сценарий уничтожения иглы при переиспользовании соединения, который и является фактическим триггером.
| Дата | Событие |
|---|
!defined(CURL_DISABLE_SMB)defined(USE_CURL_NTLM_CORE)sizeof(curl_off_t) > 4 (64-битный off_t, стандарт на большинстве платформ)SMB включён по умолчанию в сборках curl, поддерживающих NTLM.
e090be9f73a7a71459ef678c777c5209dfCVE-2026-3805 — исправлено в curl 8.19.0. Затронуты версии: 8.13.0 – 8.18.0.
Daniel Wade — GitHub — [email protected]
| 2025-04-30 | 777c5209df переводит SMB на meta hash, внося ошибку |
| 2026-03-07 | Я нахожу ошибку во время аудита безопасности |
| 2026-03-08 | Сообщено в curl через HackerOne (#3591944) |
| 2026-03-08 | curl связывается с distros@openwall |
| 2026-03-11 | Выпущен curl 8.19.0 с исправлением, опубликован CVE-2026-3805 |