curl · Report #4124722
WebSocket send queue use-after-free during nested curl_ws_send()
How it was found
At lib/ws.c:1734, ws_flush() borrows a pointer from the outgoing sendbuf and passes it to ws_send_raw_blocking(); it skips those bytes only after the send returns. A curl_ws_send() from the WebSocket write callback can be interrupted by a TCP-trace debug callback that calls curl_ws_send() again, and the nested flush frees the chunk the outer send is using. On curl-8_23_0 with ENABLE_DEBUG=OFF, ASan reports a 32,767-byte use-after-free read in send(), while the no-nested control is clean. In a Release run, the first decoded frame differs at byte 32,754, a duplicate frame header appears inside it at raw offset 32,768, and 32,767 bytes from a test allocation that reused the freed chunk reach the server in the raw tail. This path does not use curl_easy_pause() and survives the proposed fixes for my earlier reports.
Steps to reproduce
-
ASan build, nested trigger, and no-nested control:
POC=/absolute/path/to/h1-attachments git checkout curl-8_23_0 cmake -S . -B bld-asan -DBUILD_SHARED_LIBS=OFF -DBUILD_STATIC_LIBS=ON -DENABLE_DEBUG=OFF -DCURL_DISABLE_VERBOSE_STRINGS=OFF -DCMAKE_C_FLAGS="-fsanitize=address -fno-omit-frame-pointer -g" cmake --build bld-asan --target libcurl_static -j2 sh "$POC/run.sh" "$PWD/bld-asan" nested # UAF, READ of size 32767 sh "$POC/run.sh" "$PWD/bld-asan" control # CLEAN -
Release build, canary, and control:
cmake -S . -B bld-release -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DBUILD_STATIC_LIBS=ON -DENABLE_DEBUG=OFF -DCURL_DISABLE_VERBOSE_STRINGS=OFF cmake --build bld-release --target libcurl_static -j2 sh "$POC/run.sh" "$PWD/bld-release" canary # CORRUPT, 32767-byte 0xA5 run sh "$POC/run.sh" "$PWD/bld-release" control # CLEAN -
Fixed control and regression test:
git apply "$POC/ws-send-fix-and-test.diff" cmake --build bld-asan --target libcurl_static -j2 sh "$POC/run.sh" "$PWD/bld-asan" nested # CLEANTest 2313 in the diff fails before the fix and passes after it.
Source history points to commit 02edae54e8, first included in 8.12.0; the earliest release where I reproduced the UAF is 8.15.0, while 8.10.0 was clean. I did not run 8.12.0.
Impact
A WebSocket peer can receive bytes from reused heap memory when an application uses this nested callback pattern