curl · Report #3971462
Heap-use-after-free in CURLOPT_REFERER when passed a CURLINFO_REFERER pointer
How it was found
Summary
Setting CURLOPT_REFERER with a pointer previously obtained from CURLINFO_REFERER on the same easy handle triggers a heap-use-after-free. The CURLOPT_REFERER handler frees data->state.referer (line lib/setopt.c:2089) before Curl_setstropt reads from the now-dangling pointer (line lib/setopt.c:2090→85).
The bug was introduced in commit ff300ac4aa ("setopt: defref the old referer when setting a new", closing PR #21826).
Root cause
CURLINFO_REFERER returns Curl_bufref_ptr(&data->state.referer) — a direct pointer into handle-internal heap memory allocated during redirect handling in Curl_http_follow().
The CURLOPT_REFERER handler does:
// lib/setopt.c:2085-2091
case CURLOPT_REFERER:
Curl_bufref_free(&data->state.referer); // frees the buffer
result = Curl_setstropt(data, STRING_SET_REFERER, ptr); // reads from ptr
break;
When ptr is the same pointer returned by CURLINFO_REFERER, the free on line 2089 invalidates the memory that Curl_setstropt then reads via strlen(s) on line 85.
This usage is not prohibited by the API documentation. CURLINFO_REFERER documents that the pointer is private memory the application must not free. curl_easy_setopt documents that string arguments are copied and the application does not need to retain them. Passing a valid const char * from one API to another that promises to copy it is a reasonable composition.
Reproduction
- The curl source tree at or after commit
ff300ac4aa
- Build curl at the affected commit with ASan:
cmake -S . -B build \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_C_FLAGS="-fsanitize=address -fno-omit-frame-pointer -O1" \
-DCMAKE_SHARED_LINKER_FLAGS="-fsanitize=address" \
-DBUILD_SHARED_LIBS=ON -DBUILD_CURL_EXE=OFF -DBUILD_TESTING=OFF \
-DCURL_USE_OPENSSL=ON
cmake --build build -j$(nproc)
- Compile the PoC:
cc -fsanitize=address -fno-omit-frame-pointer -O1 \
poc.c -Iinclude -Lbuild/lib -Wl,-rpath,build/lib -lcurl -o poc
- Start the server and run both paths:
python3 server.py &
sleep 1
# Control (strdup'd copy) — succeeds cleanly:
./poc control
# Aliased pointer — triggers heap-use-after-free:
./poc vulnerable
Expected vs. actual
- Control path: completes with
SETOPT_RESULT=0. No ASan errors. - Aliased path: ASan reports
heap-use-after-freeinstrlencalled fromCurl_setstropt(lib/setopt.c:85), on memory freed byCurl_bufref_freeatlib/setopt.c:2089.
ERROR: AddressSanitizer: heap-use-after-free
READ of size 3
#0 ... in strlen
#1 ... in Curl_setstropt lib/setopt.c:85
#2 ... in setopt_cptr_http_mqtt lib/setopt.c:2090
freed by thread T0 here:
#1 ... in Curl_bufref_free lib/bufref.c:61
#2 ... in setopt_cptr_http_mqtt lib/setopt.c:2089
previously allocated by thread T0 here:
#8 ... in curl_url_get lib/urlapi.c:1713
#9 ... in Curl_http_follow lib/http.c:1224
Impact
The immediate effect is an invalid heap read during strlen. In an unsanitized build, libcurl may crash or copy stale heap contents into its Referer value, potentially sending those bytes in a later HTTP request.
Triggering requires all of:
CURLOPT_FOLLOWLOCATION+CURLOPT_AUTOREFERERenabled.- The application retrieves the auto-generated referer via
CURLINFO_REFERER. - The application passes that exact pointer back to
CURLOPT_REFERERon the same handle.
A remote peer cannot trigger this in an arbitrary libcurl application without the application performing this pointer round-trip. This is an application-side usage pattern rather than a remotely exploitable primitive, which limits the practical attack surface.