HexBrain

curl · Report #3971462

Heap-use-after-free in CURLOPT_REFERER when passed a CURLINFO_REFERER pointer

Informativestatus
lowseverity
7upvotes
14 Sept 2026disclosed

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
  1. 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)
  1. Compile the PoC:
cc -fsanitize=address -fno-omit-frame-pointer -O1 \
  poc.c -Iinclude -Lbuild/lib -Wl,-rpath,build/lib -lcurl -o poc
  1. 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-free in strlen called from Curl_setstropt (lib/setopt.c:85), on memory freed by Curl_bufref_free at lib/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:

  1. CURLOPT_FOLLOWLOCATION + CURLOPT_AUTOREFERER enabled.
  2. The application retrieves the auto-generated referer via CURLINFO_REFERER.
  3. The application passes that exact pointer back to CURLOPT_REFERER on 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.

This report was disclosed publicly on HackerOne and belongs to its author and the program. See the original