Skip to content

Commit e2834c8

Browse files
authored
fix(tracing): avoid curl's getenv calls (#3528)
1 parent 14dc0a4 commit e2834c8

3 files changed

Lines changed: 128 additions & 1 deletion

File tree

ext/coms.c

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,88 @@ typedef uint32_t group_id_t;
4747

4848
#define GROUP_ID_PROCESSED (1UL << 31UL)
4949

50+
/* Snapshot of proxy-related environment variables taken during MINIT.
51+
* These are later applied as explicit libcurl options on the Datadog
52+
* writer handle so that libcurl does not call curl_getenv() from
53+
* background threads.
54+
*
55+
* Lifetime: allocated once per module load and freed during MSHUTDOWN
56+
* via ddtrace_coms_mshutdown_proxy_env() after all writer threads and
57+
* curl handles have been torn down.
58+
*/
59+
static char *ddtrace_env_no_proxy = NULL; /* no_proxy / NO_PROXY */
60+
static char *ddtrace_env_http_proxy = NULL; /* http_proxy / HTTP_PROXY */
61+
static char *ddtrace_env_https_proxy = NULL; /* https_proxy / HTTPS_PROXY */
62+
static char *ddtrace_env_all_proxy = NULL; /* all_proxy / ALL_PROXY */
63+
5064
ddtrace_coms_state_t ddtrace_coms_globals = {.stacks = NULL};
5165
static struct ddog_AgentRemoteConfigWriter_ShmHandle *dd_agent_config_writer;
5266
struct ddog_ShmHandle *ddtrace_coms_agent_config_handle;
5367

68+
static void ddtrace_store_env(char **target, const char *value) {
69+
if (!*target && value && value[0]) {
70+
*target = strdup(value);
71+
}
72+
}
73+
74+
/* Snapshot the proxy-related environment variables curl itself consults.
75+
*
76+
* Generic pattern in libcurl (see url.c):
77+
* - For scheme-specific proxies, it looks up "<scheme>_proxy" in lowercase
78+
* first and, for most schemes, falls back to the uppercase variant
79+
* (e.g., "https_proxy" then "HTTPS_PROXY"). If no scheme-specific proxy
80+
* is found, it falls back to "all_proxy"/"ALL_PROXY".
81+
* - For the no-proxy list, it consults both "no_proxy" and "NO_PROXY".
82+
*
83+
* There is a special case in libcurl for HTTP: to avoid header-based
84+
* injection in CGI/server contexts, it does *not* automatically fall back
85+
* from "http_proxy" to "HTTP_PROXY". We still snapshot both here, but when
86+
* we later configure our agent handle we only ever use the cached value
87+
* explicitly, not libcurl's own HTTP_PROXY fallback.
88+
*/
89+
void ddtrace_coms_minit_proxy_env(void) {
90+
const char *v;
91+
92+
/* no_proxy / NO_PROXY */
93+
v = getenv("no_proxy");
94+
if (!v || !v[0]) {
95+
v = getenv("NO_PROXY");
96+
}
97+
ddtrace_store_env(&ddtrace_env_no_proxy, v);
98+
99+
/* http_proxy / HTTP_PROXY */
100+
v = getenv("http_proxy");
101+
if (!v || !v[0]) {
102+
v = getenv("HTTP_PROXY");
103+
}
104+
ddtrace_store_env(&ddtrace_env_http_proxy, v);
105+
106+
/* https_proxy / HTTPS_PROXY */
107+
v = getenv("https_proxy");
108+
if (!v || !v[0]) {
109+
v = getenv("HTTPS_PROXY");
110+
}
111+
ddtrace_store_env(&ddtrace_env_https_proxy, v);
112+
113+
/* all_proxy / ALL_PROXY */
114+
v = getenv("all_proxy");
115+
if (!v || !v[0]) {
116+
v = getenv("ALL_PROXY");
117+
}
118+
ddtrace_store_env(&ddtrace_env_all_proxy, v);
119+
}
120+
121+
void ddtrace_coms_mshutdown_proxy_env(void) {
122+
free(ddtrace_env_no_proxy);
123+
ddtrace_env_no_proxy = NULL;
124+
free(ddtrace_env_http_proxy);
125+
ddtrace_env_http_proxy = NULL;
126+
free(ddtrace_env_https_proxy);
127+
ddtrace_env_https_proxy = NULL;
128+
free(ddtrace_env_all_proxy);
129+
ddtrace_env_all_proxy = NULL;
130+
}
131+
54132
static bool _dd_is_memory_pressure_high(void) {
55133
ddtrace_coms_stack_t *stack = atomic_load(&ddtrace_coms_globals.current_stack);
56134
if (stack) {
@@ -746,7 +824,46 @@ void ddtrace_curl_set_connect_timeout(CURL *curl) {
746824

747825
static void ddtrace_curl_set_hostname_generic(CURL *curl, const char *path) {
748826
char *url = ddtrace_agent_url();
827+
828+
/* Prevent libcurl from reading no_proxy/NO_PROXY in worker threads:
829+
* always set CURLOPT_NOPROXY on this handle. If the env had a value
830+
* we cached it and reuse it; otherwise we set it to the empty string,
831+
* which means "no entries", but still suppresses curl_getenv().
832+
*/
833+
if (ddtrace_env_no_proxy) {
834+
curl_easy_setopt(curl, CURLOPT_NOPROXY, ddtrace_env_no_proxy);
835+
} else {
836+
curl_easy_setopt(curl, CURLOPT_NOPROXY, "");
837+
}
838+
749839
if (url && url[0]) {
840+
const char *proxy = NULL;
841+
842+
/* Choose a proxy based on the agent URL scheme, using the cached
843+
* env values. This mirrors curl's detect_proxy() precedence:
844+
* - scheme-specific proxy if present
845+
* - otherwise all_proxy / ALL_PROXY
846+
*
847+
* This happens without calling getenv() in the worker thread.
848+
*/
849+
if (strncmp(url, "http://", 7) == 0) {
850+
if (ddtrace_env_http_proxy) {
851+
proxy = ddtrace_env_http_proxy;
852+
} else if (ddtrace_env_all_proxy) {
853+
proxy = ddtrace_env_all_proxy;
854+
}
855+
} else if (strncmp(url, "https://", 8) == 0) {
856+
if (ddtrace_env_https_proxy) {
857+
proxy = ddtrace_env_https_proxy;
858+
} else if (ddtrace_env_all_proxy) {
859+
proxy = ddtrace_env_all_proxy;
860+
}
861+
}
862+
863+
if (proxy) {
864+
curl_easy_setopt(curl, CURLOPT_PROXY, proxy);
865+
}
866+
750867
char *http_url = url;
751868
if (strlen(url) > 7 && strncmp(url, "unix://", 7) == 0) {
752869
curl_easy_setopt(curl, CURLOPT_UNIX_SOCKET_PATH, url + 7);

ext/coms.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ bool ddtrace_coms_minit(size_t initial_stack_size, size_t max_payload_size, size
6262
void ddtrace_coms_mshutdown(void);
6363
void ddtrace_coms_curl_shutdown(void);
6464
void ddtrace_coms_rshutdown(void);
65+
void ddtrace_coms_minit_proxy_env(void);
66+
void ddtrace_coms_mshutdown_proxy_env(void);
6567
uint32_t ddtrace_coms_next_group_id(void);
6668
void ddtrace_coms_set_test_session_token(const char *token, size_t token_len);
6769

@@ -86,6 +88,7 @@ uint32_t ddtrace_coms_test_msgpack_consumer(void);
8688

8789
/* exposed for diagnostics {{{ */
8890
void ddtrace_curl_set_hostname(CURL *curl);
91+
void ddtrace_curl_set_telemetry_url(CURL *curl);
8992
void ddtrace_curl_set_timeout(CURL *curl);
9093
void ddtrace_curl_set_connect_timeout(CURL *curl);
9194
/* }}} */

ext/ddtrace.c

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1518,9 +1518,13 @@ static PHP_MINIT_FUNCTION(ddtrace) {
15181518
ddtrace_limiter_create();
15191519
ddtrace_standalone_limiter_create();
15201520

1521+
#ifndef _WIN32
1522+
/* Snapshot proxy-related env vars once at startup to avoid getenv()
1523+
* from the background writer thread inside libcurl. */
1524+
ddtrace_coms_minit_proxy_env();
1525+
15211526
ddtrace_log_minit();
15221527

1523-
#ifndef _WIN32
15241528
ddtrace_dogstatsd_client_minit();
15251529
#endif
15261530
ddshared_minit();
@@ -1586,6 +1590,9 @@ static PHP_MSHUTDOWN_FUNCTION(ddtrace) {
15861590
if (ddtrace_coms_flush_shutdown_writer_synchronous()) {
15871591
ddtrace_coms_curl_shutdown();
15881592
}
1593+
/* All writer threads and curl handles are gone at this point, so
1594+
* it is safe to free the cached proxy env strings for ASan. */
1595+
ddtrace_coms_mshutdown_proxy_env();
15891596
} else /* ! part of the if outside the ifdef */
15901597
#endif
15911598
if (get_global_DD_TRACE_FORCE_FLUSH_ON_SHUTDOWN() && ddtrace_sidecar) {

0 commit comments

Comments
 (0)