mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-06-11 09:07:15 +00:00
On Linux/glibc, getaddrinfo_with_timeout() runs DNS asynchronously via getaddrinfo_a(GAI_NOWAIT) using a stack-local gaicb. When gai_suspend() hits the connection timeout, gai_cancel() is called and the function returns immediately — but gai_cancel() is non-blocking and can return EAI_NOTCANCELED, leaving the resolver worker thread alive and still referencing the destroyed stack frame. Adds three opt-in gtest cases (GetAddrInfoAsyncCancelTest.*) that exercise the cancel path repeatedly. They are gated on Linux/glibc + CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO at compile time, and on the CPPHTTPLIB_TEST_ISSUE_2431=1 env var at runtime, so a normal `make test` run is unaffected. Also adds a dedicated CI job (issue-2431-repro) and a Docker-based local runner (test/run_issue_2431_repro.sh) that sinkhole UDP/53 so the timeout branch is taken, and run the test under ASAN/LSAN. With the bug present these runs are expected to fail; with a fix applied they should pass. Refs: https://github.com/yhirose/cpp-httplib/issues/2431 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
76 lines
2.7 KiB
Bash
Executable File
76 lines
2.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Reproducer runner for Issue #2431
|
|
# (https://github.com/yhirose/cpp-httplib/issues/2431).
|
|
#
|
|
# Spins up an Ubuntu container, points the resolver at a fake nameserver
|
|
# that never replies (so getaddrinfo_a actually hits its timeout), builds
|
|
# the test suite with g++ + ASAN, and runs the GetAddrInfoAsyncCancelTest
|
|
# cases.
|
|
#
|
|
# Expected outcomes:
|
|
# - HEAD prior to the fix: ASAN reports a use-after-free / heap-buffer
|
|
# overflow during one of the GetAddrInfoAsyncCancelTest cases.
|
|
# - HEAD with the fix applied: all three cases PASS.
|
|
#
|
|
# Usage:
|
|
# bash test/run_issue_2431_repro.sh
|
|
#
|
|
# Requirements: Docker (Linux container support). The container needs
|
|
# --privileged because the test binary uses `setarch -R` to disable ASLR
|
|
# for ASAN compatibility, and because the script binds UDP/53 inside the
|
|
# container.
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
|
|
docker run --rm --privileged \
|
|
-v "$REPO_ROOT:/work" \
|
|
-w /work/test \
|
|
ubuntu:24.04 bash -c '
|
|
set -euo pipefail
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
|
|
apt-get update -qq
|
|
apt-get install -y -qq --no-install-recommends \
|
|
ca-certificates g++ make pkg-config iptables util-linux coreutils file \
|
|
libssl-dev zlib1g-dev libbrotli-dev libzstd-dev libcurl4-openssl-dev \
|
|
>/dev/null
|
|
|
|
# Force DNS-only resolution: Ubuntu defaults nsswitch.conf to
|
|
# "hosts: files mdns4_minimal [NOTFOUND=return] dns ...", which
|
|
# short-circuits to NOTFOUND before reaching glibc DNS code, so the
|
|
# gai_cancel() branch never gets exercised.
|
|
sed -i "s/^hosts:.*/hosts: dns/" /etc/nsswitch.conf
|
|
|
|
# Drop all outbound UDP/53 traffic so DNS queries hang silently — this
|
|
# matches the iptables-based setup in the original reproducer for
|
|
# Issue #2431. Drop incoming responses too, in case anything sneaks
|
|
# through (defense in depth).
|
|
iptables -I OUTPUT -p udp --dport 53 -j DROP
|
|
iptables -I INPUT -p udp --sport 53 -j DROP
|
|
trap "iptables -D OUTPUT -p udp --dport 53 -j DROP 2>/dev/null; iptables -D INPUT -p udp --sport 53 -j DROP 2>/dev/null" EXIT
|
|
|
|
# Sanity check: a real DNS lookup must hang (and time out) now.
|
|
if timeout 2 getent hosts example.com >/dev/null 2>&1; then
|
|
echo "ERROR: DNS unexpectedly resolved — DROP / nsswitch is not in effect" >&2
|
|
exit 1
|
|
fi
|
|
echo "[ok] DNS UDP/53 is being dropped (expected for the repro)"
|
|
|
|
cd /work/test
|
|
echo "=== building test binary (g++ + ASAN) ==="
|
|
make CXX=g++ test 2>&1 | tail -5
|
|
|
|
ARCH=$(uname -m)
|
|
echo "=== running GetAddrInfoAsyncCancelTest with CPPHTTPLIB_TEST_ISSUE_2431=1 ==="
|
|
set +e
|
|
CPPHTTPLIB_TEST_ISSUE_2431=1 setarch "$ARCH" -R \
|
|
./test --gtest_filter="GetAddrInfoAsyncCancelTest.*" 2>&1
|
|
rc=$?
|
|
set -e
|
|
echo "=== test exit: $rc ==="
|
|
exit $rc
|
|
'
|