run_issue_2431_repro.sh 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. #!/usr/bin/env bash
  2. # Reproducer runner for Issue #2431
  3. # (https://github.com/yhirose/cpp-httplib/issues/2431).
  4. #
  5. # Spins up an Ubuntu container, runs the loopback DNS test fixture
  6. # (test/dns_test_fixture.py), routes the container's DNS lookups to
  7. # that fixture via an iptables NAT rule, builds the test suite with
  8. # g++ + ASAN, and runs the GetAddrInfoAsyncCancelTest cases.
  9. #
  10. # Expected outcomes:
  11. # - HEAD prior to the fix: ASAN reports stack-use-after-return inside
  12. # getaddrinfo_with_timeout's getaddrinfo_a path during one of the
  13. # GetAddrInfoAsyncCancelTest cases.
  14. # - HEAD with the fix applied: all three cases PASS.
  15. #
  16. # Usage:
  17. # bash test/run_issue_2431_repro.sh
  18. #
  19. # Requirements: Docker (Linux container support). The container needs
  20. # --privileged because the test binary uses `setarch -R` to disable ASLR
  21. # for ASAN compatibility, and because the test job manages iptables
  22. # rules inside the container.
  23. set -euo pipefail
  24. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  25. REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
  26. docker run --rm --privileged \
  27. -v "$REPO_ROOT:/work" \
  28. -w /work/test \
  29. ubuntu:24.04 bash -c '
  30. set -euo pipefail
  31. export DEBIAN_FRONTEND=noninteractive
  32. apt-get update -qq
  33. apt-get install -y -qq --no-install-recommends \
  34. ca-certificates g++ make pkg-config iptables iproute2 util-linux coreutils file \
  35. python3 \
  36. libssl-dev zlib1g-dev libbrotli-dev libzstd-dev libcurl4-openssl-dev \
  37. >/dev/null
  38. # Force DNS-only resolution: Ubuntu defaults nsswitch.conf to
  39. # "hosts: files mdns4_minimal [NOTFOUND=return] dns ...", which
  40. # short-circuits to NOTFOUND before reaching glibc DNS code, so the
  41. # gai_cancel() branch never gets exercised.
  42. sed -i "s/^hosts:.*/hosts: dns/" /etc/nsswitch.conf
  43. # Start the loopback DNS test fixture (delayed UDP responder).
  44. DNS_FIXTURE_PORT=15353
  45. DNS_FIXTURE_DELAY=3
  46. python3 /work/test/dns_test_fixture.py "$DNS_FIXTURE_PORT" "$DNS_FIXTURE_DELAY" \
  47. >/tmp/dns_fixture.log 2>&1 &
  48. FIXTURE_PID=$!
  49. # Route the container DNS lookups to the fixture; conntrack handles the
  50. # reply path automatically. /etc/resolv.conf is left untouched.
  51. iptables -t nat -I OUTPUT -p udp --dport 53 \
  52. -j REDIRECT --to-port "$DNS_FIXTURE_PORT"
  53. trap '"'"'iptables -t nat -F OUTPUT 2>/dev/null || true; kill "$FIXTURE_PID" 2>/dev/null || true'"'"' EXIT
  54. # Wait for the fixture to start listening.
  55. for _ in $(seq 1 50); do
  56. if ss -lun "( sport = :$DNS_FIXTURE_PORT )" | grep -q ":$DNS_FIXTURE_PORT"; then
  57. break
  58. fi
  59. sleep 0.1
  60. done
  61. ss -lun "( sport = :$DNS_FIXTURE_PORT )" | grep -q ":$DNS_FIXTURE_PORT" || {
  62. echo "ERROR: dns_test_fixture failed to start" >&2
  63. cat /tmp/dns_fixture.log >&2 || true
  64. exit 1
  65. }
  66. # Sanity check: a DNS lookup must take at least the fixture delay
  67. # (proving the NAT rule routes the query to the fixture).
  68. start=$(date +%s)
  69. getent hosts unresolvable-host.invalid >/dev/null 2>&1 || true
  70. elapsed=$(( $(date +%s) - start ))
  71. if [ "$elapsed" -lt 2 ]; then
  72. echo "ERROR: lookup returned in ${elapsed}s; fixture not in DNS path" >&2
  73. exit 1
  74. fi
  75. echo "[ok] DNS lookups are routed to the test fixture (took ${elapsed}s)"
  76. cd /work/test
  77. echo "=== building test binary (g++ + ASAN) ==="
  78. make CXX=g++ test 2>&1 | tail -5
  79. ARCH=$(uname -m)
  80. echo "=== running GetAddrInfoAsyncCancelTest with CPPHTTPLIB_TEST_ISSUE_2431=1 ==="
  81. set +e
  82. CPPHTTPLIB_TEST_ISSUE_2431=1 \
  83. ASAN_OPTIONS=detect_stack_use_after_return=1 \
  84. setarch "$ARCH" -R \
  85. ./test --gtest_filter="GetAddrInfoAsyncCancelTest.*" 2>&1
  86. rc=$?
  87. set -e
  88. echo "=== test exit: $rc ==="
  89. exit $rc
  90. '